使用subprocess.call()获取输出结果

333

我如何获取使用subprocess.call()运行的进程的输出?

将一个StringIO.StringIO对象传递给stdout会出现以下错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/subprocess.py", line 444, in call
    return Popen(*popenargs, **kwargs).wait()
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/subprocess.py", line 588, in __init__
    errread, errwrite) = self._get_handles(stdin, stdout, stderr)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/subprocess.py", line 945, in _get_handles
    c2pwrite = stdout.fileno()
AttributeError: StringIO instance has no attribute 'fileno'
>>> 

2
Mike的答案是正确的。请注意,StringIO在大多数情况下像文件一样工作,但不是所有情况都是如此。在你的情况下它无法工作,因为multiprocessing模块在某些情况下假定实际的文件存在。这个问题可能已经被修复了:请参见http://bugs.python.org/issue5313以获取相关错误信息。 - Michael Greene
实际上,communicate() 使用的是 select.select(),它只接受文件描述符,所以这并不是一个错误。当我第一次遇到它并深入探索 subprocess.py 时,我感到非常困惑,但也学到了很多! - Mike
1
我认为subprocess.run自Python 3.5版本起使得这个过程更简单了。我有机会时会添加一个答案。 - Mark Amery
1
请注意,已接受的答案已经过时了。Python 2.7 的简单答案是 subprocess.check_output();在 Python 3.5+ 中,您还需要查看 subprocess.run()。如果可以避免使用原始的 subprocess.Popen(),则不应该或不想使用它,尽管某些更复杂的用例需要它(然后您必须自己围绕它进行所需的管道工作)。Stack Overflow subprocess 标签信息页面提供了一些适用于较不常见情况的良好资源。 - tripleee
7个回答

338
如果您的Python版本大于等于2.7,您可以使用subprocess.check_output,它基本上就是您想要的(它将标准输出作为字符串返回)。
简单示例(Linux版本,请参见注释):
import subprocess

print subprocess.check_output(["ping", "-c", "1", "8.8.8.8"])

请注意,ping命令使用的是Linux符号(-c表示计数)。如果您在Windows上尝试此操作,请记得将其更改为-n以获得相同的结果。
如下评论所述,您可以在此其他答案中找到更详细的解释。

42
找到了一个带有可行代码的答案。如果你使用了它,请点赞。 - yurisich
9
请注意,如果进程返回非零退出代码,check_output将抛出异常。 - tobltobs
请注意,check_outputrun 一样存在管道填充问题,因为它只是调用run。因此,如果您的进程生成了更多输出,它将无限期地挂起。来自@Mike@JabbaPopen解决方案运行得更好。 - ChrisWue
但另一个答案并没有演示如何从check_call中读取stdout。 - Ray Salemi
你可以执行 subprocess.check_output(['ls", "/']).decode() 来获取通常以二进制形式打印的内容。 - ozgeneral
显示剩余3条评论

234

subprocess.call() 的输出应该只重定向到文件。

你应该改用subprocess.Popen()。然后,您可以为stderr、stdout和/或stdin参数传递subprocess.PIPE,并使用communicate()方法从管道中读取:

from subprocess import Popen, PIPE

p = Popen(['program', 'arg1'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
output, err = p.communicate(b"input data that is passed to subprocess' stdin")
rc = p.returncode

原因在于subprocess.call()使用的文件对象必须具有真正的文件描述符,因此实现fileno()方法。仅仅使用任何文件对象是不够的。

更多信息请参见这里


60
这个页面(http://docs.python.org/library/subprocess.html#module-subprocess)不建议使用`subprocess.PIPE`,你有什么办法可以克服这个问题吗? - Vladimir Keleshev
6
同时,问题中要求使用subprocess.call函数,而Mike的回答实际上使用了Popen函数,因为subprocess.call只返回返回代码,而没有访问任何流的方法。这是在使用2.6版本时的情况。如果使用2.7版本,则可以使用@Sergi的回答。 - Willyfrog
20
@Halst:文档警告在call()调用中不要使用PIPE(管道)。可以在subprocess.Popen中使用PIPE,例如此答案所建议的那样:output, _ = Popen(..., stdout=PIPE).communicate() - jfs
6
简而言之,除非你使用管道,否则不要使用PIPEcall()相当于Popen().wait(),因此它不会消耗管道(一旦相关的操作系统管道缓冲区填满,子进程将永远挂起)。如果使用了PIPE,则Popen().communicate()会读/写管道中的数据,从而允许子进程继续运行。 - jfs
2
//,啊,好的。这很有道理。奇怪的是它甚至允许PIPE作为参数。无论如何,我是一个好的StackOverFlow公民,所以我为此提出了一个问题:https://dev59.com/slwY5IYBdhLWcg3wpJP_ 你愿意把那个作为答案吗? - Nathan Basanese
显示剩余5条评论

105

对于Python 3.5+,建议使用subprocess模块的run函数。它返回一个CompletedProcess对象,您可以轻松地获取输出以及返回代码。

from subprocess import PIPE, run

command = ['echo', 'hello']
result = run(command, stdout=PIPE, stderr=PIPE, universal_newlines=True)
print(result.returncode, result.stdout, result.stderr)

这是一个很棒的答案。但不要忽略 'stdout=PIPE' 这一部分,就像我第一次做时那样,否则输出将非常短暂! - excyberlabber
3
应该使用"text=True"代替"universal_newlines=True",因为前者是为了向后兼容而保留的。 - tobiasraabe
4
建议使用run函数,但如果使用这些参数时出现问题,可能是Windows的问题。在run命令中添加shell=True即可解决。无论你使用什么操作系统,如果有Python 3.7+,请将stdout=PIPE, stderr=PIPE替换为capture_output=True以添加一个缓冲区,避免死锁。 - Post169
3
很棒的回答适用于Python 3.7+用户!所以我运行了 rc = subprocess.run(ogrList, capture_output=True) 然后 print("\n", rc.stdout),它非常有效!谢谢! - grego

53

我有以下解决方案。它可以捕获执行外部命令的退出代码、标准输出和标准错误流:

import shlex
from subprocess import Popen, PIPE

def get_exitcode_stdout_stderr(cmd):
    """
    Execute the external command and get its exitcode, stdout and stderr.
    """
    args = shlex.split(cmd)

    proc = Popen(args, stdout=PIPE, stderr=PIPE)
    out, err = proc.communicate()
    exitcode = proc.returncode
    #
    return exitcode, out, err

cmd = "..."  # arbitrary external command, e.g. "python mytest.py"
exitcode, out, err = get_exitcode_stdout_stderr(cmd)

我也在博客上写了一篇关于它的文章这里

编辑: 解决方案已更新为不需要写入临时文件的较新方案。


2
@Jabba,不知道为什么,除非我在Popen()的参数中添加了 shell=True,否则它无法工作。你能解释一下为什么吗? - JaeGeeTee
@JaeGeeTee:你想要调用的命令是什么?我猜你想要调用一个包含管道符的命令(例如"cat file.txt | wc -l")。 - Jabba
@Jabba 我正在运行 ping 127.0.0.1 - JaeGeeTee
@JaeGeeTee - 尝试明确使用 /bin/ping 命令? - cdyson37
并没有真正回答如何在输出非常小的情况下使用call或check_call的问题。 - Ray Salemi
我们在读取 out 和 err 之前不是必须先执行 proc.wait() 吗? - infoclogged

31

我最近刚刚学会了如何做到这一点,这里是来自我当前项目的一些示例代码:

#Getting the random picture.
#First find all pictures:
import shlex, subprocess
cmd = 'find ../Pictures/ -regex ".*\(JPG\|NEF\|jpg\)" '
#cmd = raw_input("shell:")
args = shlex.split(cmd)
output,error = subprocess.Popen(args,stdout = subprocess.PIPE, stderr= subprocess.PIPE).communicate()
#Another way to get output
#output = subprocess.Popen(args,stdout = subprocess.PIPE).stdout
ber = raw_input("search complete, display results?")
print output
#... and on to the selection process ...

现在你已经将命令的输出存储在名为“output”的变量中。"stdout = subprocess.PIPE"告诉类从Popen中创建一个名为'stdout'的文件对象。就我所知,communicate()方法只是一种方便的方式,返回运行的进程的输出和错误的元组。另外,在实例化Popen时即运行该进程。


并没有真正回答如何明智地使用call的问题。 - Ray Salemi

29

关键是使用函数subprocess.check_output

例如,下面的函数捕获进程的stdout和stderr,并返回是否调用成功。它兼容Python 2和3:

from subprocess import check_output, CalledProcessError, STDOUT

def system_call(command):
    """ 
    params:
        command: list of strings, ex. `["ls", "-l"]`
    returns: output, success
    """
    try:
        output = check_output(command, stderr=STDOUT).decode()
        success = True 
    except CalledProcessError as e:
        output = e.output.decode()
        success = False
    return output, success

output, success = system_call(["ls", "-l"])

如果你想将命令作为字符串而不是数组传递,请使用这个版本:

from subprocess import check_output, CalledProcessError, STDOUT
import shlex

def system_call(command):
    """ 
    params:
        command: string, ex. `"ls -l"`
    returns: output, success
    """
    command = shlex.split(command)
    try:
        output = check_output(command, stderr=STDOUT).decode()
        success = True 
    except CalledProcessError as e:
        output = e.output.decode()
        success = False
    return output, success

output, success = system_call("ls -l")

没问题,很容易就找到了。顺便说一下,它还有一个input=选项,可以指定要管道传输到命令的输入,而不是用Popen超级复杂的方式来完成。 - sudo
非常好,因为它可以处理非零退出代码! - Jason
完全做到了这一点,正是我想要的方式。并澄清Jason的评论:如果使用非零退出代码,则会进入异常处理程序部分,因此您的“success”将是“False”。 - retif

16

Ipython shell 中:

In [8]: import subprocess
In [9]: s=subprocess.check_output(["echo", "Hello World!"])
In [10]: s
Out[10]: 'Hello World!\n'

基于sargue的答案。感谢sargue提供的帮助。


1
s=subprocess.check_output(["echo", "Hello World!"]); print(s) 打印出来是 b'Hello World!\n',我该如何去掉 b'' 索引?@jhegedus - alper
1
尝试运行 b'Hello World!\n'.decode("utf-8")。 - Soumyajit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接