检查 python 中正在运行的子进程的标准输出

Posted

技术标签:

【中文标题】检查 python 中正在运行的子进程的标准输出【英文标题】:Check on the stdout of a running subprocess in python 【发布时间】:2017-07-22 01:32:41 【问题描述】:

如果需要定期检查正在运行的进程的stdout。例如,进程是tail -f /tmp/file,它是在python脚本中产生的。然后每 x 秒,该子进程的标准输出被写入一个字符串并进一步处理。子进程最终被脚本停止。

解析子进程的stdout,如果到目前为止使用check_output,这似乎不起作用,因为该进程仍在运行并且不会产生明确的输出。

>>> from subprocess import check_output
>>> out = check_output(["tail", "-f", "/tmp/file"])
 #(waiting for tail to finish)

应该可以为子进程使用线程,以便可以处理多个子进程的输出(例如tail -f /tmp/file1,tail -f /tmp/file2)。

如何启动子进程、定期检查和处理其标准输出并最终以多线程友好的方式停止子进程? python脚本在Linux系统上运行。

目标不是连续读取文件,tail 命令就是一个示例,因为它的行为与实际使用的命令完全相同。

编辑:我没有想到这一点,该文件不存在。 check_output 现在只是等待进程完成。

edit2:另一种方法,PopenPIPE 似乎会导致相同的问题。它等待tail 完成。

>>> from subprocess import Popen, PIPE, STDOUT
>>> cmd = 'tail -f /tmp/file'
>>> p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
>>> output = p.stdout.read()
 #(waiting for tail to finish)

【问题讨论】:

***.com/a/6482200/1866177 可能解决了这个问题。 您的示例存在比无法读取标准输出更大的问题。请修复它。 @Dschoni,OP 正在尝试摄取,而不是重定向输出。这使它比您提供的链接更完整。 您应该使用完整形式的Popen,因为该进程将连续运行,锁定到标准输出管道并在文件对象上使用for 循环(因为它是一个阻塞迭代器) .正如您所提到的,如果您的主程序除了响应文件中的更改之外还需要做一些事情,那么读取循环可能需要在单独的线程上运行。 当您通过实际尝试修复您的示例时,我将发布完整的答案。 【参考方案1】:

您的第二次尝试正确率为 90%。唯一的问题是您试图在完成后同时读取tail 的标准输出的全部。但是,tail 旨在(无限期地?)在后台运行,因此您真的想逐行读取标准输出:

from subprocess import Popen, PIPE, STDOUT
p = Popen(["tail", "-f", "/tmp/file"], stdin=PIPE, stdout=PIPE, stderr=STDOUT)
for line in p.stdout:
    print(line)

我已删除 shell=Trueclose_fds=True 参数。第一个是不必要的并且有潜在危险,而第二个只是默认设置。

请记住,文件对象在 Python 中的行上是可迭代的。 for 循环将一直运行,直到 tail 死亡,但它会处理出现的每一行,而不是 read,后者将阻塞直到 tail 死亡。

如果我在/tmp/file 创建一个空文件,启动这个程序并开始使用另一个shell 将行回显到文件中,程序将回显这些行。您可能应该用更有用的东西替换print

这是我在启动上面的代码后键入的命令示例:

命令行

$ echo a > /tmp/file
$ echo b > /tmp/file
$ echo c >> /tmp/file

程序输出(来自不同 shell 中的 Python)

b'a\n'
b'tail: /tmp/file: file truncated\n'
b'b\n'
b'c\n'

如果您希望主程序在响应tail 的输出时响应,请在单独的线程中启动循环。你应该使这个线程成为一个守护进程,这样即使tail 没有完成,它也不会阻止你的程序退出。您可以让线程打开子进程,也可以将标准输出传递给它。我更喜欢后一种方法,因为它可以让您在主线程中获得更多控制:

def deal_with_stdout():
    for line in p.stdout:
        print(line)

from subprocess import Popen, PIPE, STDOUT
from threading import Thread
p = Popen(["tail", "-f", "/tmp/file"], stdin=PIPE, stdout=PIPE, stderr=STDOUT)
t = Thread(target=deal_with_stdout, daemon=True)
t.start()
t.join()

这里的代码几乎相同,只是增加了一个新线程。我在最后添加了一个join(),这样程序就可以很好地作为示例运行(join 等待线程在返回之前终止)。您可能希望将其替换为您通常会运行的任何处理代码。

如果您的线程足够复杂,您可能还想从Thread 继承并覆盖run 方法,而不是传入简单的target

【讨论】:

感谢您详尽的解释,这对我很有帮助! 我很高兴它做到了。一旦您知道如何使用多线程和多处理,它是一个相当简单的工具,但很难跳入其中。对我来说最糟糕的是术语。

以上是关于检查 python 中正在运行的子进程的标准输出的主要内容,如果未能解决你的问题,请参考以下文章

持久的子进程管道 - 没有读取标准输出

从提升的子进程获取错误和标准输出

无法在 Windows 上使用 Python 终止正在运行的子进程

为远程进程的子进程捕获标准输出

Jupyter notebook 中 Python 子进程的实时标准输出输出

如何在 Windows 中检查正在运行的 shell?