【问题标题】:Get the output of multiple commands from subprocess.Popen从 subprocess.Popen 获取多个命令的输出
【发布时间】:2014-04-17 19:20:32
【问题描述】:

我正在尝试运行一个命令,获取它的输出,然后在同一环境中运行另一个命令(例如,如果我在第一个命令中设置了一个环境变量,我希望它可用于第二个命令)。我试过这个:

import subprocess

process = subprocess.Popen("/bin/bash", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE);

process.stdin.write("export MyVar=\"Test\"\n")
process.stdin.write("echo $MyVar\n")
process.stdin.flush()

stdout, stderr  = process.communicate()
print "stdout: " + str(stdout)

# Do it again
process.stdin.write("echo $MyVar\n")
process.stdin.flush()

stdout, stderr = process.communicate()
print "stdout: " + str(stdout)

但是communicate() 一直读到最后,所以这不是一个有效的技术。 (我明白了:)

stdout: Test

Traceback (most recent call last):
  File "./MultipleCommands.py", line 15, in <module>
    process.stdin.write("echo $MyVar\n")
ValueError: I/O operation on closed file

我见过这个:https://stackoverflow.com/a/15654218/284529,但它没有给出如何做它建议的工作示例。谁能演示如何做到这一点? 我还看到了其他涉及在循环中不断检查输出的技术,但这不符合“获取命令的输出”的心态 - 它只是将其视为流。

【问题讨论】:

  • 对于您正在谈论的具体示例,听起来您最好使用 Popen 采用的 env kwarg:Popen("echo $MyVar"], env={"MyVar": "Test"})
  • @dano 你是对的。在实际情况下,我可能想启动一个 ssh 会话,并保持打开状态并继续向其发出命令,以避免为每个命令建立新连接的开销。
  • 在这种情况下,我建议使用 paramiko:github.com/paramiko/paramiko
  • 您能否为我的个人调查回答一个问题:subprocess 文档中的哪个位置让您认为process.communicate() 可能会在同一过程中被多次调用?对于交互使用,pexpectsubprocess 更适合。如果你想通过 ssh 运行命令;考虑使用fabric 作为库(它比paramiko 更高级)。
  • 没有什么让我明确地认为它没问题,它似乎是我在子进程中唯一能找到的“得到命令的输出”,而不是无限期地从管道中读取。我会研究一下“面料”,谢谢。

标签: python subprocess


【解决方案1】:

当使用communicate 时,它会看到子进程已经结束,但如果你有一个中间进程 (bash),当你的子子进程结束时,你必须以某种方式手动发出信号。

至于其余部分,最简单的方法是发出一条标记线。但是,很抱歉在这里让您失望,但池化(即不断检查循环)实际上是 the only sane option。如果您不喜欢循环,可以将其“隐藏”在函数中。

import subprocess
import time

def readlines_upto(stream, until="### DONE ###"):
    while True:
        line = stream.readline()
        if line is None:
            time.sleep(0.1)
            continue
        if line.rstrip() == until:
            break
        yield line

process = subprocess.Popen("/bin/bash", shell=True,
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process.stdin.write("export MyVar=\"Test\"\n")
process.stdin.write("echo $MyVar\n")
process.stdin.write("echo '### DONE ###'\n")
process.stdin.flush()

# Note, I don't read stderr here, so if subprocess outputs too much there,
# it'll fill the pipe and stuck. If you don't need stderr data, don't
# redirect it to a pipe at all. If you need it, make readlines read two pipes.
stdout = "".join(line for line in readlines_upto(process.stdout))
print "stdout: " + stdout

# Do it again
process.stdin.write("echo $MyVar\n")
process.stdin.flush()
stdout, stderr = process.communicate()
print "stdout: " + str(stdout)

【讨论】:

    【解决方案2】:

    要获得多个命令的输出,只需将它们组合成一个脚本即可:

    #!/usr/bin/env python
    import subprocess
    import sys
    
    output = subprocess.check_output("""
    export MyVar="Test"
    echo $MyVar
    echo ${MyVar/est/ick}
    """, shell=True, executable='/bin/bash', universal_newlines=True)
    sys.stdout.write(output)
    

    输出

    Test
    Tick
    

    【讨论】:

    • Sebastian 但是你不知道输出的哪一部分对应于每个命令。
    • @DavidDoria:但是直接使用 p.stdin/p.stdout 也不会提供此信息(您如何为给定命令读取多少行?)
    【解决方案3】:

    Popen对象的communicatewait方法,进程返回后关闭PIPE。如果您想与流程保持沟通,请尝试以下操作:

    import subprocess
    
    process = subprocess.Popen("/bin/bash", shell=True, stdin=subprocess.PIPE,       stdout=subprocess.PIPE, stderr=subprocess.PIPE);
    
    process.stdin.write("export MyVar=\"Test\"\n")
    process.stdin.write("echo $MyVar\n")
    process.stdin.flush()
    
    process.stdout.readline()
    
    process.stdin.write("echo $MyVar\n")
    process.stdin.flush()
    
    stdout, stderr = process.communicate()
    print "stdout: " + str(stdout)
    

    我认为你误解了沟通......

    查看此链接:- http://docs.python.org/library/subprocess.html#subprocess.Popen.communicate

    communicate 向另一个进程发送一个字符串,然后等待它完成......(就像你说的等待 EOF 监听 stdout 和 stderror)

    你应该做的是:

    proc.stdin.write('message')
    
    # ...figure out how long or why you need to wait...
    
    proc.stdin.write('message2')
    

    (如果您需要获取 stdout 或 stderr,您可以使用 proc.stdout 或 proc.stderr)

    【讨论】:

    • Moshin 那只会得到第一个命令输出的一行,对吧?
    • 这个适用于Windows cmd.exe吗?
    • 不确定,我只在linux下测试过
    【解决方案4】:

    按照说明书:

    Popen.communicate(input=None)

        与进程交互:将数据发送到标准输入。从 stdout 和 stderr 读取数据,直到到达文件结尾。 等待进程 终止。 [...]

    您需要改为从管道中读取:

    import os
    stdout = os.read(process.stdout.fileno(), 1024)
    print "stdout: " + stdout
    

    如果没有数据在等待,它将永远挂在那里或直到准备好读取数据。您应该使用select 系统调用来防止这种情况发生:

    import select
    import os
    
    try:
        i,o,e = select.select([process.stdout], [], [], 5) # 5 second timeout
        stdout = os.read(i[0].fileno(), 1024)
    except IndexError:
        # nothing was written to the pipe in 5 seconds
        stdout = ""
    
    print "stdout: " + stdout
    

    如果你想获取多个写入,为了避免竞争条件,你必须把它放在一个循环中:

    stdout = ""
    while True:
        try:
            i,o,e = select.select([process.stdout], [], [], 5) # 5 second timeout
            stdout += os.read(i[0].fileno(), 1024)
        except IndexError:
            # nothing was written to the pipe in 5 seconds, we're done here
            break
    

    【讨论】:

    • 我试过这个,但如果每个命令多于一行,它似乎只会将第一行放入标准输出。例如:gist.github.com/daviddoria/fac54d3e8f2e01d21036
    • 这是因为在写入和从管道读取之间可能存在延迟。例如,如果我在本地运行您的要点,有时我会得到一行,有时会得到两行。这是一个竞赛条件。为避免它,您需要将其放入循环中,如果达到超时,则需要 break。在事先不知道您将要接收的数据量的情况下,您唯一能做的就是依靠超时。我更新了答案。
    猜你喜欢
    • 1970-01-01
    • 2015-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-16
    • 1970-01-01
    • 2013-12-01
    • 2013-09-06
    相关资源
    最近更新 更多