python 子进程模块的动态输出

Dynamic output from python subprocess module

如何在 python 中使用子进程模块动态实现输出(而外部程序保持 运行)。我想从中动态获取输出的外部程序是 ngrok , ngrok keep 运行 只要我的程序是 运行 但我需要在进程是 运行 时输出,这样我就可以提取新生成的 "forwarding url"

当我尝试这样做时:

cmd = ['ngrok', 'http', '5000']
output = subprocess.Popen(cmd, stdout=subprocess.PIPE, buffersize=1)

它一直在缓冲区中存储输出

我知道这是重复的,但我现在找不到任何相关的话题。我得到的只是 output.communicate().

下面是一个可能有用的片段:

import subprocess
cmd = ['ngrok', 'http', '5000']
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

while process.poll() is None:
    print(process.stdout.readline())
print(process.stdout.read())
process.stdout.close()

这将通过您的脚本将进程输出的任何内容输出到您的输出中。它通过在输出前寻找换行符来实现。

如果不是因为 ngrok 使用 ncurses and/or 将输出独占到它自己的 user/thread 就像 SSH 要求ssh user@host.

时输入密码

process.poll() 检查进程是否有退出代码(如果它死了),如果没有,它继续循环并打印进程 stdout.

的任何内容

还有其他(更好的)方法来解决这个问题,但这是我能给你的最低限度,而且不会很快变得复杂。

例如,process.stdout.read() 可以与 select.select() 结合使用,以在换行时获得更好的缓冲输出。因为如果 \n 永远不会出现,上面的示例可能会挂起您的整个应用程序。

这里有很多缓冲区陷阱,在您手动执行此类操作之前需要了解这些缓冲区陷阱。否则,请改用 process.communicate()

编辑:要避开 ngrok 使用的 I/O 的 hogging/limitation,您可以使用 pty.fork() 并通过 os.read 模块读取子标准输出:

#!/usr/bin/python

## Requires: Linux
## Does not require: Pexpect

import pty, os
from os import fork, waitpid, execv, read, write, kill

def pid_exists(pid):
    """Check whether pid exists in the current process table."""
    if pid < 0:
        return False
    try:
        kill(pid, 0)
    except (OSError, e):
        return e.errno == errno.EPERMRM
    else:
        return True

class exec():
    def __init__(self):
        self.run()

    def run(self):
        command = [
                '/usr/bin/ngrok',
                'http',
                '5000'
        ]

        # PID = 0 for child, and the PID of the child for the parent    
        pid, child_fd = pty.fork()

        if not pid: # Child process
            # Replace child process with our SSH process
            execv(command[0], command)

        while True:
            output = read(child_fd, 1024)
            print(output.decode('UTF-8'))
            lower = output.lower()

            # example input (if needed)
            if b'password:' in lower:
                write(child_fd, b'some response\n')
        waitpid(pid, 0)

exec()

这里还有一个问题,我不太确定是什么或为什么会这样。
我猜这个过程正在等待 signal/flush 某种方式。
问题是它只打印 ncurses 的第一个 "setup data",这意味着它擦除屏幕并设置背景颜色。

但这至少会给你过程的输出。替换 print(output.decode('UTF-8')) 会告诉你输出是什么。