将子进程的stdout / stderr重定向到文件

时间:2014-07-10 21:28:00

标签: python stdout stderr child-process

我有一个Python脚本(popen.py),它运行另一个Python脚本(counter.py)作为子进程,输出重定向到/tmp/counter.log。我正在使用的代码是:

/tmp/counter.py

#!/usr/bin/env python2
import time

i = 0
while True:
    print i
    i +=1
    time.sleep(1)

/tmp/popen.py

#!/usr/bin/env python2
import subprocess

f = open("/tmp/counter.log", "a+")
p = subprocess.Popen("/tmp/counter.py", stdout=f, stderr=f, bufsize=1)

然而,当我运行popen.py时,子进程被创建并保持运行,但是在输出达到4096字节之前没有任何内容写入/tmp/counter.log,然后它似乎被刷新到文件。

有没有办法让我的子进程逐行写入日志文件而不修改counter.py脚本本身?

我不想修改counter.py的原因是子进程可能并不总是运行Python脚本。我尝试过运行一个小的可执行文件(用C编写)同样的问题,同样的问题也出现了。

我已尝试为该文件编写自我刷新包装,并按照here所述使用stdout,但这也不起作用。

我已经使用lsofstrace进行了一些调试,这是我设法找到的:

lsof(文件描述符)

手动运行/tmp/counter.py

COMMAND PID   USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
python2 629 daniel    0u   CHR  136,0      0t0      3 /dev/pts/0
python2 629 daniel    1u   CHR  136,0      0t0      3 /dev/pts/0
python2 629 daniel    2u   CHR  136,0      0t0      3 /dev/pts/0

通过/tmp/popen.py

运行/tmp/counter.py
COMMAND PID   USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
python2 638 daniel    0u   CHR  136,0      0t0      3 /dev/pts/0
python2 638 daniel    1u   REG  202,0        0    768 /tmp/counter.log
python2 638 daniel    2u   REG  202,0        0    768 /tmp/counter.log

strace(while循环期间的系统调用)

手动运行/tmp/counter.py

select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
write(1, "11\n", 3)                     = 3
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
write(1, "12\n", 3)                     = 3
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
write(1, "13\n", 3)                     = 3
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
write(1, "14\n", 3)                     = 3
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
write(1, "15\n", 3)                     = 3

通过/tmp/popen.py

运行/tmp/counter.py
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})     = 0 (Timeout)
...
write(1, "11\n12\n13\n14\n15\n16\n17\n18\n"..., 4096) = 4096

3 个答案:

答案 0 :(得分:1)

我最终使用的解决方案并没有完全解决问题,但此时最可接受的折衷方案是在生成子进程时设置PYTHONUNBUFFERED环境变量:

#!/usr/bin/env python2
import subprocess

f = open("/tmp/counter.log", "a+")
p = subprocess.Popen("/tmp/counter.py", stdout=f, stderr=f, env={
    "PYTHONUNBUFFERED": "Yes please"
})

这在额外代码和其他进程方面的开销最低,但仅在子进程是Python脚本时才有效。

答案 1 :(得分:0)

通常,您不能让进程逐行写入文件,除非进程定期刷新。但是你可以让调用过程看起来像一个终端。遵循CLIB规则的进程将切换到行模式并为您提供所需的内容。在这个例子中,我设置为伪终端并写入+刷新日志文件。

#!/usr/bin/env python2

import os
import subprocess
import pty

master,slave = pty.openpty()
f = open("/tmp/counter.log", "a+")
p = subprocess.Popen(["python", "counter.py"], stdout=slave, stderr=slave, close_fds=True)
os.close(slave)
reader = os.fdopen(master)
while True:
    data = reader.readline()
    if not data:
        break
    f.write(data)
    f.flush()
    print data.strip()
print 'done'
reader.close()
p.wait()

答案 2 :(得分:-1)

实际上,subprocess.Popen可用于Python脚本以外的可执行文件。这是一个片段,它创建了用户的cron时间表的副本:

import subprocess
import shlex

def getTempCrontabFile(argTmpFile='/tmp/tmpFile'):
    # Create a file in r/w mode that will be the target for
    # the crontab utility redirection.
    try:
        tmpFile = open(argTmpFile, 'a+')
    except IOError as customErr:
        print 'Failed to open or create temporary crontab file.'
        print customErr
        return customErr
    # Define the command line to list the cron schedule.
    cmdLine = 'crontab -l'
    # Format the command line into an array of arguments. This is
    # useful for proper formatting of spaces and quoted arguments
    # especially when commands get complicated.
    args = shlex.split(cmdLine)
    # Make the call to Popen using the file we created for stdout.
    result = subprocess.Popen(args, stdout=tmpFile)
    return result
相关问题