因此,您希望记录进程或子进程的stdout和stderr(单独),如果您没有记录任何内容,则输出与您在终端中看到的不同。
看起来很简单没有?不幸的是,似乎可能无法为这个问题编写一个通用的解决方案,这个解决方案适用于任何给定的流程......
管道重定向是一种分离stdout和stderr的方法,允许您单独记录它们。不幸的是,如果将stdout / err更改为管道,则进程可能会检测到管道不是tty(因为它没有宽度/高度,波特率等)并且可能会相应地更改其行为。为什么改变行为?好吧,一些开发人员利用终端的功能,如果你正在写一个文件,这些功能是没有意义的。例如,加载条通常要求将终端光标移回到行的开头,并使用新长度的条覆盖前一个加载条。颜色和字体粗细也可以显示在终端中,但是在平面ASCII文件中它们不能。如果您要将此类程序的stdout直接写入文件,则该输出将包含所有终端ANSI转义码,而不是正确格式化的输出。因此,开发人员实现某种类型的#34; isatty"在向stdout / err写入任何内容之前检查,因此如果该检查返回false,它可以为文件提供更简单的输出。
这里通常的解决方案是通过使用pty来欺骗这些程序,使管道实际上是tt - 一个也具有宽度,高度等的双向管道。您将进程的所有输入/输出重定向到此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()
请注意,解决方案应该适用于任何进程,而不仅仅是此代码。覆盖sys / os模块并使用LD_PRELOAD是克服挑战的非常有趣的方法,但它们无法解决问题的核心:)
答案 0 :(得分:21)
喜欢这个吗?
% ./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 <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;
}
答案 1 :(得分:5)
对于更简单的用例(例如开发测试),请使用dtruss
(linux)或stdout
(OSX)。当然,这不会在特权过程中发挥作用。
以下是一个示例,您可以将stderr
fd1与$ 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 +++
fd2区分开来:
standard xxx data
在上面的示例中,您会看到每个strace
加倍,因为您无法重定向stdout / stderr。但是,您可以要求stdout
将其输出保存到文件中。
从理论上讲,如果stderr
和#!/bin/bash
if [ "$RACK_ENV" == "production" ]; then
echo Using qgsocksify to wrap Puma
bin/qgsocksify bundle exec puma -C config/puma.rb
else
echo Using straight Puma
bundle exec puma -C config/puma.rb
fi
引用同一个终端,则只能在进程的上下文中区分2,或者在用户模式下(LD_PRELOAD),或内核空间(strace工具使用的ptrace接口)。一旦数据到达实际设备,真实的伪,就会失去区别。
答案 2 :(得分:1)
您始终可以分配Pseudo-TTY,这是screen
的作用。
在Python中,您可以使用pty.openpty()
这个&#34;主人&#34;代码通过了你的测试:
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,你的&#34;奴隶&#34;过程将看到不同的PYT名称:
inp = pty.openpty()
oup = pty.openpty()
erp = pty.openpty()
subprocess.Popen([command, args], stdin=inp[1], stdout=uop[1], stderr=erp[1])
答案 3 :(得分:0)
$ PYTHONPATH=/tmp/python:$PYTHONPATH ./challenge.py
$ cat stdout
This is a real tty :)
standard output data
$ cat stderr
standard error data
由于此脚本会导入os
模块,因此我在os
中创建了自己的/tmp/python
模块,并将/tmp/python
添加到sys.path
。
os.py
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
答案 4 :(得分:0)
何时可以使用脚本命令:
$ script --return -c "[executable string]" >stdout 2>stderr