J.J*_*J.J 35 python unix linux tty pty
因此,您希望记录进程或子进程的stdout和stderr(单独),如果您没有记录任何内容,则输出与您在终端中看到的不同.
看起来很简单没有?不幸的是,看起来可能无法为这个问题编写一个通用的解决方案,这个解决方案适用于任何给定的流程......
管道重定向是一种分离stdout和stderr的方法,允许您单独记录它们.不幸的是,如果将stdout/err更改为管道,则进程可能会检测到管道不是tty(因为它没有宽度/高度,波特率等)并且可能会相应地更改其行为.为什么改变行为?好吧,一些开发人员利用终端的功能,如果您要写入文件,这些功能没有意义.例如,加载条通常要求将终端光标移回到行的开头,并使用新长度的条覆盖前一个加载条.颜色和字体粗细也可以显示在终端中,但是在平面ASCII文件中它们不能.如果您要将这样的程序的stdout直接写入文件,那么该输出将包含所有终端ANSI转义码,而不是正确格式化的输出.因此,开发人员在向stdout/err写入任何内容之前会执行某种"isatty"检查,因此如果该检查返回false,它可以为文件提供更简单的输出.
这里通常的解决方案是通过使用pty来欺骗这些程序,使管道实际上是ttys - 一个也具有宽度,高度等的双向管道.你将进程的所有输入/输出重定向到这个pty,这就是欺骗进程思考它与真实终端的对话(你可以直接将其记录到文件中).唯一的问题是,通过对stdout和stderr使用单个pty,我们现在无法再区分这两者.
所以你可能想为每个管道尝试不同的pty - 一个用于stdin,一个用于stdout,一个用于stderr.虽然这将在50%的时间内工作,但不幸的是,许多进程会执行额外的重定向检查,以确保stdout和stderr(/ dev/tty000x)的输出路径相同.如果它们不是,则必须有重定向,因此它们会给你相同的行为,就好像你没有pty管道stderr和stdout一样.
您可能认为这种过度检查重定向并不常见,但不幸的是它实际上非常普遍,因为许多程序重用其他代码进行检查,就像在OSX中找到的这些代码一样:
我认为找到解决方案的最佳方式是挑战.如果任何人都可以运行以下脚本(理想情况下通过Python,但此时我将采取任何措施),以便单独记录stdout和stderr,并且你设法欺骗它认为它是通过tty执行的,你解决了问题:)
#!/usr/bin/python
import os
import sys
if sys.stdout.isatty() and sys.stderr.isatty() and os.ttyname(sys.stdout.fileno()) == os.ttyname(sys.stderr.fileno()):
sys.stdout.write("This is a")
sys.stderr.write("real tty :)")
else:
sys.stdout.write("You cant fool me!")
sys.stdout.flush()
sys.stderr.flush()
Run Code Online (Sandbox Code Playgroud)
请注意,解决方案应该适用于任何进程,而不仅仅是此代码.覆盖sys/os模块并使用LD_PRELOAD是击败挑战的非常有趣的方法,但它们并没有解决问题的核心:)
Kar*_*wak 21
像这样?
% ./challenge.py >stdout 2>stderr
% cat stdout
This is a real tty :)
standard output data
% cat stderr
standard error data
Run Code Online (Sandbox Code Playgroud)
因为我骗了一点.;-)
% echo $LD_PRELOAD
/home/karol/preload.so
Run Code Online (Sandbox Code Playgroud)
像这样......
% gcc preload.c -shared -o preload.so -fPIC
Run Code Online (Sandbox Code Playgroud)
我现在觉得很脏,但很有趣.:d
% cat preload.c
#include <stdlib.h>
int isatty(int fd) {
if(fd == 2 || fd == 1) {
return 1;
}
return 0;
}
char* ttyname(int fd) {
static char* fake_name = "/dev/fake";
if(fd == 2 || fd == 1) {
return fake_name;
}
return NULL;
}
Run Code Online (Sandbox Code Playgroud)
对于更简单的用例(例如开发测试),请使用strace
(linux)或dtruss
(OSX).当然,这在特权过程中不起作用.
这是一个示例,您可以将stdout
fd1与stderr
fd2 区分开来:
$ strace -ewrite python2 test.py
[snip]
write(1, "This is a real tty :)\n", 22This is a real tty :)
) = 22
write(2, "standard error data", 19standard error data) = 19
write(1, "standard output data", 20standard output data) = 20
+++ exited with 0 +++
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,您看到每个standard xxx data
加倍,因为您无法重定向stdout/stderr.但是,您可以要求strace
将其输出保存到文件中.
在理论方面,如果stdout
并且stderr
引用同一个终端,则只能在进程的上下文中区分2,无论是在用户模式(LD_PRELOAD)还是内核空间(strace工具使用的ptrace接口).一旦数据到达实际设备,真实的伪,就会失去区别.