问题
因此,您想(分别)记录一个进程或子进程的stdout和stderr,如果您未记录任何内容,则输出不会与在终端中看到的输出不同。
似乎很简单,不是吗?不幸的是,似乎不可能为这个问题写一个通用的解决方案,该解决方案适用于任何给定的流程...
背景
管道重定向是一种分离stdout和stderr的方法,允许您分别记录它们。不幸的是,如果将stdout / err更改为管道,则该过程可能会检测到该管道不是tty(因为它没有宽度/高度,波特率等),并且可能会相应地更改其行为。为什么要改变行为?好吧,有些开发人员会利用终端的功能,如果您要写出文件,这些功能就没有意义。例如,加载条通常要求将终端光标移回到行的开头,而先前的加载条则需要用新长度的条覆盖。颜色和字体粗细也可以显示在终端中,但是在平面ASCII文件中则不能显示。如果将此类程序的标准输出直接写入文件,则该输出将包含所有终端ANSI转义码,而不是格式正确的输出。因此,开发人员在将任何内容写入stdout / err之前实施了某种" isatty"检查,因此,如果该检查返回false,它可以为文件提供更简单的输出。
此处通常的解决方案是通过使用pty欺骗此类程序,使其认为管道实际上是ttys-双向管道也具有宽度,高度等。您将流程的所有输入/输出重定向到该pty,从而欺骗了考虑将其与真实终端进行对话(您可以将其直接记录到文件中)。唯一的问题是,通过对stdout和stderr使用单个pty,我们现在不再能够区分两者。
因此,您可能想为每个管道尝试不同的pty-一个用于stdin,一个用于stdout,一个用于stderr。尽管这将在50%的时间中起作用,但不幸的是,许多进程还会执行其他重定向检查,以确保stdout和stderr(/ dev / tty000x)的输出路径相同。如果不是,则必须进行重定向,因此,它们的行为就如同您通过管道传输stderr和stdout时没有任何权限。
您可能会认为这种对重定向的全面检查并不常见,但是不幸的是,它实际上非常普遍,因为许多程序都使用其他代码进行检查,例如OSX中的以下代码:
http://src.gnu-darwin.org/src/bin/stty/util.c
挑战
我认为找到解决方案的最佳方法是挑战。如果任何人都可以通过以下方式运行以下脚本(最好是通过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()
请注意,解决方案应真正适用于任何过程,而不仅限于此代码。覆盖sys / os模块并使用LD_PRELOAD是克服挑战的非常有趣的方法,但是它们不能解决问题的核心:)
您可能想通过向Q栏中添加赏金来增加成功的机会。祝您好运!
好主意! 2天的冷却时间结束后,我会做的:)
我不会称其为"愚弄isatty()",但是愚弄样式checkredirect(),这要严格得多。
谢谢@CharlesDuffy,我已经更新了问题,以更好地反映我正在寻找一种适用于任何给定过程的通用解决方案,而不仅仅是此特定的代码。
像这样?
% ./challenge.py >stdout 2>stderr
% cat stdout
This is a real tty :)
standard output data
% cat stderr
standard error data
因为我有点作弊。 ;-)
% echo $LD_PRELOAD
/home/karol/preload.so
像这样
% gcc preload.c -shared -o preload.so -fPIC
我现在觉得很脏,但这很有趣。 :D
% cat preload.c
#include
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;
}
那太好了。
呵呵,这是非常棒的:D,但它绝对是作弊的:P如果不仅仅是因为El Capitan自从在系统二进制文件上从我们这里拿走DYLD_INSERT_LIBRARIES以来,它就无法在OSX上使用:((
是的,嗯,我没有其他想法。除了自己做内核工作。无论如何,您将不得不在这里"作弊"。 :D
在Python中重新定义这些功能更加容易。但是话又说回来,脚本可以对它们进行自省并找出……
是的,但是Python应该是一个简化的挑战,而要解决的实际问题是二进制文件。
对于更简单的用例(例如开发测试),请使用strace(linux)或dtruss(OSX)。当然,这不会在特权进程中起作用。
这是一个示例,您可以区分stdout fd1和stderr fd2:
$ strace -ewrite python2 test.py
[snip]
write(1,"This is a real tty :)
", 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 +++
在上面的示例中,您看到每个standard xxx data都增加了一倍,因为您无法重定向stdout / stderr。但是,您可以要求strace将其输出保存到文件中。
从理论上讲,如果stdout和stderr引用同一终端,则只能在用户环境(LD_PRELOAD)或内核空间( strace工具使用)。数据一旦击中实际设备(伪实数),就将失去区分。
优秀,只要可以忍受性能打击!
使用诸如sysdig之类的更有效的工具将有助于降低性能。
您始终可以分配伪TTY,这就是screen的作用。
在Python中,您可以使用pty.openpty()进行访问
此"主"代码通过了您的测试:
import subprocess, pty, os
m, s = pty.openpty()
fm = os.fdopen(m,"rw")
p = subprocess.Popen(["python2","test.py"], stdin=s, stdout=s, stderr=s)
p.communicate()
os.close(s)
print fm.read()
当然,如果您想区分stdin / out / err,您的"从属"进程将看到不同的PYT名称:
inp = pty.openpty()
oup = pty.openpty()
erp = pty.openpty()
subprocess.Popen([command, args], stdin=inp[1], stdout=uop[1], stderr=erp[1])
这说明了OP中提到的问题-您可以欺骗子流程,也可以拥有单独的日志记录,但不能同时拥有两者。
如果创建新的devpts装载该怎么办?
我真的不明白这意味着什么-您能详细说明吗? :)
在Windows上无法使用?
当可以使用脚本命令时:
$ script --return -c"[executable string]">stdout 2>stderr
不幸的是,我无法在OSX上进行测试,但是稍后我将在Linux上再试一次,并告诉您:)
$ PYTHONPATH=/tmp/python:$PYTHONPATH ./challenge.py
$ cat stdout
This is a real tty :)
standard output data
$ cat stderr
standard error data
由于此脚本导入了os模块,因此我在/tmp/python中创建了自己的os模块,并在/tmp/python之前添加了/tmp/python。
操作系统
import sys
sys.path.remove('/tmp/python')
this_module = sys.modules['os']
del sys.modules['os']
import os
globals().update(vars(os))
class File(file):
isatty = lambda self: True
sys.stdout = File('stdout', 'w')
sys.stderr = File('stderr', 'w')
isatty = lambda fd: True
ttyname = lambda fd: '/dev/fake'
sys.modules['os'] = this_module
我认为OP实际上希望将解决方案应用于任何程序,而不仅仅是Python。因此,monkeypatching或伪造os模块不会做到这一点:(
@ niklas-r"如果任何人都可以运行以下脚本……"那就是OP提到的内容。
我确实做到了,但是我认为没有人会认为我正在寻找一个仅欺骗了这段特定代码的解决方案-特别是通过删除os模块; P我已经更新了问题以反映这一点。