Python Popen:同时写入stdout和日志文件

ima*_*hat 38 python subprocess popen

我正在使用Popen调用一个shell脚本,该脚本不断将其stdout和stderr写入日志文件.有没有办法连续同时输出日志文件(到屏幕),或者让shell脚本同时写入日志文件和标准输出?

我基本上想在Python中做这样的事情:

cat file 2>&1 | tee -a logfile #"cat file" will be replaced with some script
Run Code Online (Sandbox Code Playgroud)

再次,这将stderr/stdout连接到tee,将它写入stdout和我的日志文件.

我知道如何在Python中将stdout和stderr写入日志文件.我被困在哪里是如何将这些复制回屏幕:

subprocess.Popen("cat file", shell=True, stdout=logfile, stderr=logfile)
Run Code Online (Sandbox Code Playgroud)

当然我可以做这样的事情,但有没有办法在没有tee和shell文件描述符重定向的情况下做到这一点?:

subprocess.Popen("cat file 2>&1 | tee -a logfile", shell=True)
Run Code Online (Sandbox Code Playgroud)

tde*_*ney 37

您可以使用管道从程序的标准输出中读取数据并将其写入您想要的所有位置:

import sys
import subprocess

logfile = open('logfile', 'w')
proc=subprocess.Popen(['cat', 'file'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in proc.stdout:
    sys.stdout.write(line)
    logfile.write(line)
proc.wait()
Run Code Online (Sandbox Code Playgroud)

UPDATE

在python 3中,该universal_newlines参数控制管道的使用方式.如果False,管道读取返回bytes对象,可能需要解码(例如line.decode('utf-8'))以获取字符串.如果True,python为你解码

版本3.3中更改:当universal_newlines为True时,类使用编码locale.getpreferredencoding(False)而不是locale.getpreferredencoding().有关此更改的更多信息,请参阅io.TextIOWrapper类.

  • 你可能需要`iter(proc.stdout.readline,'')`(由于预读缓冲区的错误)并在子进程刷新后立即添加`bufsize = 1`来打印行.调用`proc.stdout.close()`来避免fd泄漏. (6认同)
  • 您还可以创建一个类似于object的文件来封装此功能,然后在调用`Popen`时使用它来代替`stdout` /`stderr`. (5认同)
  • @sr2222 - 我也喜欢这个想法......除了现在我想到它......,它们是操作系统管道,而不是Python对象,那么这是否有效? (2认同)
  • @imagineerThis - 代码读取stdout直到它关闭然后等待程序退出.您在等待之前阅读,这样您就不会冒着管道填满和挂起程序的风险.您在读取最终程序退出并返回代码后等待.如果你不等,你会得到一个僵尸进程(至少在linux上). (2认同)
  • @tdelaney:不,这不是固定的.试试这个剧本:[`导入时间; 打印(1); time.sleep(1); 打印(2)`](http://ideone.com/8KFawl).在脚本退出之前,您的版本不会打印"1".我的注释中的"flush"一词指的是*子进程*中的缓冲区,你无法直接控制.如果孩子没有刷新它的标准输出,那么输出将被延迟.可以使用[`pexpect`,`pty` modules](http://stackoverflow.com/a/12471855/4279)或[`stdbuf`,`unbuffer`,`script`命令]修复它(http:// unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe). (2认同)

jfs*_*jfs 14

要模拟:subprocess.call("command 2>&1 | tee -a logfile", shell=True)不调用tee命令:

#!/usr/bin/env python2
from subprocess import Popen, PIPE, STDOUT

p = Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1)
with p.stdout, open('logfile', 'ab') as file:
    for line in iter(p.stdout.readline, b''):
        print line,  #NOTE: the comma prevents duplicate newlines (softspace hack)
        file.write(line)
p.wait()
Run Code Online (Sandbox Code Playgroud)

要解决可能的缓冲问题(如果输出延迟),请参阅Python中的链接:从subprocess.communicate()读取流输入.

这是Python 3版本:

#!/usr/bin/env python3
import sys
from subprocess import Popen, PIPE, STDOUT

with Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1) as p, \
     open('logfile', 'ab') as file:
    for line in p.stdout: # b'\n'-separated lines
        sys.stdout.buffer.write(line) # pass bytes as is
        file.write(line)
Run Code Online (Sandbox Code Playgroud)

  • 虽然我同意他没有要求,但似乎应该检查返回状态.我希望能在这里找到它.似乎会使答案完整.也许"应该"很强大. (5认同)
  • 你应该提一下,你可以在完成后找到p.returncode中的返回码. (2认同)
  • Python 3 以上版本:执行后不实时打印在屏幕上 (2认同)

Cir*_*四事件 5

逐字节写入终端以用于交互式应用程序

此方法立即将其获取的任何字节写入 stdout,这更接近地模拟 的行为tee,特别是对于交互式应用程序。

主要.py

#!/usr/bin/env python3
import os
import subprocess
import sys
with subprocess.Popen(sys.argv[1:], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc, \
        open('logfile.txt', 'bw') as logfile:
    while True:
        byte = proc.stdout.read(1)
        if byte:
            sys.stdout.buffer.write(byte)
            sys.stdout.flush()
            logfile.write(byte)
            # logfile.flush()
        else:
            break
exit_status = proc.returncode
Run Code Online (Sandbox Code Playgroud)

睡眠.py

#!/usr/bin/env python3
import sys
import time
for i in range(10):
    print(i)
    sys.stdout.flush()
    time.sleep(1)
Run Code Online (Sandbox Code Playgroud)

首先我们可以进行非交互式健全性检查:

./main.py ./sleep.py
Run Code Online (Sandbox Code Playgroud)

我们看到它实时计数到标准输出。

接下来,对于交互式测试,您可以运行:

./main.py bash
Run Code Online (Sandbox Code Playgroud)

然后,您键入的字符会在您键入时立即出现在终端上,这对于交互式应用程序非常重要。这是当你运行时会发生的情况:

bash | tee logfile.txt
Run Code Online (Sandbox Code Playgroud)

另外,如果您希望输出立即显示在 ouptut 文件上,那么您还可以添加:

logfile.flush()
Run Code Online (Sandbox Code Playgroud)

tee不这样做,恐怕会影响性能。您可以使用以下方法轻松测试:

tail -f logfile.txt
Run Code Online (Sandbox Code Playgroud)

相关问题:子进程命令的实时输出

在 Ubuntu 18.04、Python 3.6.7 上测试。