【问题标题】:Forward subprocess stdout does not work when stdout is redirected to a file将标准输出重定向到文件时,前向子进程标准输出不起作用
【发布时间】:2016-03-04 16:15:51
【问题描述】:

我在将子进程的标准输出转发到当前进程的标准输出时遇到问题。

这是我的 MWE 调用者代码 (runner.py):

import sys
import subprocess
import time

p = subprocess.Popen([sys.executable, "test.py"], stdout=sys.stdout)
time.sleep(10)
p.terminate()

这里是被调用者test.py的内容:

import time

while True:
    time.sleep(1)
    print "Heartbeat"

以下将工作并将所有心跳打印到控制台:

python runner.py

但是,以下不起作用,输出文本文件仍然为空(使用 Python 2.7):

python runner.py > test.txt

我该怎么办?

【问题讨论】:

  • shell=True 可能会修复它。
  • @zondo 不,我不想包含 shell 命令

标签: python subprocess


【解决方案1】:

当标准输出为 TTY(终端)时,sys.stdout 默认为行缓冲:您打印的每一行都会立即写入 TTY。

但是当标准输出是文件时,sys.stdout 是块缓冲的:只有在打印一定数量的数据时才会将数据写入文件。通过使用p.terminate(),您将在刷新缓冲区之前终止进程。

print 之后使用sys.stdout.flush() 就可以了:

import sys
import time

while True:
    time.sleep(1)
    print "Heartbeat"
    sys.stdout.flush()

如果您使用的是 Python 3,还可以使用 print 函数的 flush 参数,如下所示:

import time

while True:
    time.sleep(1)
    print("Heartbeat", flush=True)

或者,您还可以为SIGTERM 设置一个处理程序,以确保在调用p.terminate() 时刷新缓冲区:

import signal
signal.signal(signal.SIGTERM, sys.stdout.flush)

【讨论】:

  • 1- 在整个程序中手动调用sys.stdout.flush() 很容易出错(而且效率低下)。您可以取消缓冲整个过程的标准输出,而不是 2-signal(signal.SIGTERM, sys.stdout.flush) 将不起作用。您可能指的是signal(signal.SIGTERM, lamba *a: (sys.stdout.flush(), sys.exit(-signal.SIGTERM)))。虽然它可能更简单,call p.send_signal(signal.SIGINT) instead of p.terminate() in the parent.
  • @J.F.Sebastian:刷新输出实际上是方式。您不必相信我:查看pingtail 或系统中定期写入输出的所有其他工具
  • 为什么你认为grep--line-buffered 参数?为什么默认情况下它不按照您的建议在每行之后刷新?
【解决方案2】:

可以通过在每次打印后执行sys.stdout.flush() 来强制刷新,但这很快就会变得很麻烦。由于您知道您正在运行 Python,因此可以强制 Python 进入无缓冲模式 - 使用 -u 开关或 PYTHONUNBUFFERED 环境变量:

p = subprocess.Popen([sys.executable, '-u', 'test.py'], stdout=sys.stdout)

import os
# force all future python processes to be unbuffered
os.environ['PYTHONUNBUFFERED'] = '1'

p = subprocess.Popen([sys.executable, 'test.py'])

【讨论】:

  • 您可以将env=dict(os.environ, PYTHONUNBUFFERED=1) 传递给Popen() 以避免影响其他python 进程。
【解决方案3】:

您不需要传递stdout=sys.stdout,除非sys.stdout 使用与python 可执行文件开始时使用的文件描述符不同的文件描述符。 C stdout fd 默认是继承的:你不需要做任何事情来让子进程继承它。

As @Andrea Corbellini said,如果输出被重定向到文件,那么python 使用块缓冲模式,"Heartbeat"*10(通常)太小而不会溢出缓冲区。
我希望python 在退出时刷新其内部标准输出缓冲区,但它不会在SIGTERM 信号(由.terminate() 调用生成)上执行此操作。
要让子进程优雅退出,请使用SIGINT (Ctrl+C) 而不是p.terminate()

p.send_signal(signal.SIGINT)

在这种情况下,test.py 将刷新缓冲区,您将在test.txt 文件中看到输出。要么丢弃 stderr,要么在子级中捕获 KeyboardInterrupt 异常。

如果您想在子进程仍在运行时查看输出,请运行python -u,以禁用缓冲或设置PYTHONUNBUFFERED envvar 以将所有受影响的python 进程的行为更改为@Antti Haapala suggested

注意:您的父进程也可能会缓冲输出。如果您没有及时刷新缓冲区,那么输出打印之前 test.py 甚至可能会出现之后 其输出在test.txt 文件中。父进程和子进程中的缓冲区是独立的。手动刷新缓冲区或确保在每个进程中使用适当的缓冲模式。见Disable output buffering

【讨论】:

    猜你喜欢
    • 2022-12-14
    • 2012-11-08
    • 1970-01-01
    • 1970-01-01
    • 2013-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多