如何在重定向FD后写入标准输出流?

4
请看下面的Python代码。
so = open('/tmp/test.log', 'a+')
os.dup2(so.fileno(), sys.stdout.fileno())

执行完这段代码后,我仍然希望有可能将一些内容打印到标准输出(stdout)上。
我已经尝试过:
print('Foo\n', file=sys.__stdout__)

根据文档所述,这可能是一种解决方法。

sys.__stdin__
sys.__stdout__
sys.__stderr__

这些对象包含程序开始时stdin、stderr和stdout的原始值。它们在最终化期间使用,可以用于打印到实际的标准流上,而无论sys.std*对象是否已被重定向。

但它并不是这种情况。它仍然记录在我的test.log文件中。

Python版本:3.4.8

非常感谢您的任何帮助。


1
通常(与Python无关)的解决方法是使用dup2()将原始stdout FD复制到另一个位置,然后再进行覆盖操作。例如,在bash中,你需要运行exec 4>&1来让FD 4成为FD 1的副本,以便在exec >/tmp/test.log之后能够还原FD 1的原始值。 - Charles Duffy
1
请注意,sys.__stdout__ 指向 FD 1,但如果您覆盖 FD 1,则 Python 解释器将拥有一个文件描述符的句柄,该句柄不再指向原始 stdout -- 您在解释器层下方更改了该 FD 的目标,在操作系统级别上。您的 Python 文件对象指向操作系统级别的文件描述符;这些描述符确定给定对象实际连接到哪个操作系统级别的文件(套接字、流等)... 因此,当您切换 FD 时,您会更改连接到它的 所有 Python 对象。 - Charles Duffy
1
(附注:在bash中,“exec 4>&1”实际上进行了两个独立的“fdup()”,第二个是从“fdup()”返回的动态分配的FD到“4”的;如果您只想复制一次并存储动态分配的数字,则需要使用bash 4.1或更高版本才能运行“exec {stdout_copy_fd}>&1”,将动态分配描述符的编号放入名为“stdout_copy_fd”的变量中。) - Charles Duffy
1个回答

7
sys.__stdout__无法正常工作的原因是使用了os.dup2()替换了原始stdout的文件描述符,因此任何基于它的Python文件对象都将打印到新打开的文件中。当使用dup2时,旧的stdout实际上会被关闭,因此无法恢复它。
如果您想使所有使用sys.stdout的Python代码都打印到/tmp/test.log中,请打开一个新文件并将其分配给sys.stdout
sys.stdout = open('/tmp/test.log', 'a+')

原始的 sys.stdout 仍然可用作 sys.__stdout__
如果您还想重定向任何直接写入到 sys.stdout.fileno() 的代码,包括使用 os.system()subprocess.call() 启动的子进程,同时保留对原始 stdout 的访问权限,则会变得更加复杂:您需要首先 dup() stdout 并保存它,然后使用您的 dup2() 调用来替换 FD 1:
saved_stdout_fd = os.dup(sys.stdout.fileno())
saved_stdout = os.fdopen(saved_stdout_fd,'w')
so = open('/tmp/test.log', 'a+')
os.dup2(so.fileno(), sys.stdout.fileno())

现在,sys.stdout会输出到/tmp/test.log文件中,而saved_stdout会写回原来的标准输出。

是的,我需要重定向子进程。这就是为什么我要替换文件描述符的原因。 所以复制原始的stdout实际上是唯一的方法吗? @Charles Duffy - Dawid Fieluba
@DawidFieluba,如果你只想改变一个子进程的标准输出(stdout),可以传递 stdout= 参数给 subprocess.Popen;这会在 fork() 分离子进程之后,在实际调用另一个程序的 execve() 之前改变一些事情,因此你主要解释器上的FDs不需要被修改,即使是暂时的也不需要。 - Charles Duffy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接