问题
因此,您希望(分别)记录一个进程或子进程的stdout和stderr,如果不记录任何内容,则输出不会与在终端中看到的不同
看起来很简单不?不幸的是,似乎不可能为这个问题编写一个通用的解决方案,在任何给定的过程中都能工作
背景
管道重定向是分离stdout和stderr的一种方法,允许您单独记录它们。不幸的是,如果将stdout/err更改为管道,进程可能会检测到管道不是tty(因为它没有宽度/高度、波特率等),并可能相应地更改其行为。为什么要改变这种行为?嗯,有些开发人员利用终端的功能,如果您正在向文件写入数据,这些功能就没有意义了。例如,加载条通常要求将终端光标移回行的开头,并用新长度的条覆盖上一个加载条。此外,颜色和字体重量可以显示在终端中,但在平面ASCII文件中则不能。如果要将这样一个程序的标准输出直接写入一个文件,那么该输出将包含所有终端ANSI转义码,而不是正确格式化的输出。因此,开发人员在将任何内容写入stdout/err之前实现某种类型的“isatty”检查,因此如果该检查返回false,它可以为文件提供更简单的输出
这里通常的解决方案是,通过使用pty(一种也具有宽度、高度等的双向管道)欺骗此类程序,使其认为管道实际上是TTY。您可以将进程的所有输入/输出重定向到此pty,从而欺骗进程,使其认为它与真正的终端进行了通信(您可以将其直接记录到文件)。唯一的问题是,通过对stdout和stderr使用一个pty,我们现在无法区分这两个
因此,您可能希望为每个管道尝试不同的pty—一个用于标准输入,一个用于标准输出,另一个用于标准输出。虽然这将在50%的时间内工作,但不幸的是,许多进程会执行额外的重定向检查,以确保stdout和stderr(/dev/tty000x)的输出路径相同。如果不是,则必须有重定向,因此它们给您的行为与您在没有pty的情况下通过管道传输stderr和stdout的行为相同
您可能会认为这种对重定向的过度检查是不常见的,但不幸的是,它实际上相当普遍,因为许多程序重复使用其他代码进行检查,如OSX中的以下代码:
http://src.gnu-darwin.org/src/bin/stty/util.c
挑战
我认为找到解决办法的最好方式是挑战。如果有人能够以这样一种方式运行以下脚本(理想情况下是通过Python,但现在我将采用任何方式),即分别记录stdout和stderr,并且您设法让它认为它是通过tty执行的,那么您就解决了问题:)
#/usr/bin/python
导入操作系统
导入系统
如果sys.stdout.isatty()和sys.stderr.isatty()以及os.ttyname(sys.stdout.fileno())==os.ttyname(sys.stderr.fileno()):
sys.stdout.write(“这是一个”)
sys.stderr.write(“real tty:)”)
其他:
sys.stdout.write(“你骗不了我!”)
sys.stdout.flush()
sys.stderr.flush()
请注意,解决方案应该真正适用于任何流程,而不仅仅是此代码。覆盖sys/os模块并使用LD_预加载是战胜挑战的非常有趣的方法,但它们并不能解决问题的核心:)
像这样
%./challenge.py>;标准装置2>;标准错误
%猫粪
这是一个真正的tty:)
标准输出数据
%猫标准杆
标准误差数据
因为我有点作弊
%echo$LD\u预加载
/home/karol/preload.so
就像这样
%gcc preload.c-shared-o preload.so-fPIC
我现在觉得很脏,但很有趣D
%cat preload.c
#包括<;stdlib.h>;
int isatty(int fd){
如果(fd==2 | | fd==1){
返回1;
}
返回0;
}
char*ttyname(int-fd){
静态字符*fake_name=“/dev/fake”;
如果(fd==2 | | fd==1){
返回假名称;
}
返回NULL;
}