运行Shell命令并捕获输出

1399
我希望编写一个函数,能够执行Shell命令并将其输出作为字符串返回,无论是错误消息还是成功消息,我只想获得与命令行相同的结果。
你可以参考以下代码示例:
例如:
def run_command(cmd):
    # ??????

print run_command('mysqladmin create test -uroot -pmysqladmin12')
# Should output something like:
# mysqladmin: CREATE DATABASE failed; error: 'Can't create database 'test'; database exists'

3
相关内容:https://dev59.com/OXA85IYBdhLWcg3wJf8Z - jfs
1
在 https://dev59.com/3VsW5IYBdhLWcg3w8q1S 上的重复问题解释了为什么你不能在这里使用 os.system,如果这是你真正的问题。 - tripleee
25个回答

1873
在所有官方维护的Python版本中,最简单的方法是使用subprocess.check_output函数:
>>> subprocess.check_output(['ls', '-l'])
b'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

check_output 运行只接受参数作为输入的单个程序。1 它返回与 stdout 中打印的结果完全相同的结果。如果您需要向 stdin 写入输入,请跳到 runPopen 部分。如果您想执行复杂的 shell 命令,请参阅本答案末尾关于 shell=True 的注释。

check_output 函数适用于所有官方维护版本的 Python。但是,对于较新版本,有一种更灵活的方法可供选择。

Python 的现代版本(3.5 或更高):run

如果您使用的是 Python 3.5+,并且不需要向后兼容,则官方文档推荐使用新的 run 函数来完成大多数任务。它为 subprocess 模块提供了非常通用的高级 API。要捕获程序的输出,请将 subprocess.PIPE 标志传递给 stdout 关键字参数。然后访问返回的 CompletedProcess 对象的 stdout 属性:

>>> import subprocess
>>> result = subprocess.run(['ls', '-l'], stdout=subprocess.PIPE)
>>> result.stdout
b'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

返回值是一个bytes对象,所以如果你想要一个正常的字符串,你需要进行decode操作。假设被调用的进程返回一个UTF-8编码的字符串:
>>> result.stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

这可以通过一行代码进行压缩:
>>> subprocess.run(['ls', '-l'], stdout=subprocess.PIPE).stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

如果您想将输入传递给进程的stdin,可以将bytes对象传递给input关键字参数:
>>> cmd = ['awk', 'length($0) > 5']
>>> ip = 'foo\nfoofoo\n'.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=ip)
>>> result.stdout.decode('utf-8')
'foofoo\n'

你可以使用 stderr=subprocess.PIPE (将输出捕获到 result.stderr)或 stderr=subprocess.STDOUT(将输出与常规输出一起捕获到 result.stdout)来捕获错误。如果希望在进程返回非零退出代码时抛出异常,可以传递 check=True。(或者可以检查上面的resultreturncode属性。) 当安全性不是问题时,也可以通过传递shell=True来运行更复杂的shell命令,如本答案末尾所述。
Python的后续版本进一步简化了上述内容。在Python 3.7+中,上述单行代码可以这样写:
>>> subprocess.run(['ls', '-l'], capture_output=True, text=True).stdout
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

使用这种方式的run相比旧方法稍微复杂了一点。但是现在你可以只用run函数完成几乎所有需要做的事情。

Python旧版本(3-3.4):更多关于check_output

如果你正在使用旧版本的Python,或需要一定的向后兼容性,你可以使用check_output函数,如上所述简要介绍。它自Python 2.7以来就可用。

subprocess.check_output(*popenargs, **kwargs)  

它接受与 Popen 相同的参数(请参见下文),并返回一个包含程序输出的字符串。本答案开头有一个更详细的用法示例。在 Python 3.5+ 中,check_output 等效于使用 check=Truestdout=PIPE 执行 run,并仅返回 stdout 属性。
您可以传递 stderr=subprocess.STDOUT 来确保错误消息包含在返回的输出中。当安全性不是问题时,您还可以通过传递 shell=True 来运行更复杂的 shell 命令,如本答案末尾所述。
如果您需要从 stderr 进行管道传输或向进程传递输入,则 check_output 将无法胜任。在这种情况下,请参见下面的 Popen 示例。

复杂应用程序和 Python 的旧版本 (2.6 及以下):Popen

如果您需要深度向后兼容性,或者需要比 check_outputrun 提供的更复杂的功能,则必须直接使用 Popen 对象,该对象封装了子进程的低级 API。

Popen构造函数接受一个不带参数的单个命令,或者包含一个命令作为其第一项的列表,后跟任意数量的参数,每个参数作为列表中的单独项。shlex.split可以帮助将字符串解析为适当格式的列表。 Popen对象还接受大量不同的参数以进行进程IO管理和低级配置。

要发送输入并捕获输出,communicate几乎始终是首选方法。如下所示:

output = subprocess.Popen(["mycmd", "myarg"], 
                          stdout=subprocess.PIPE).communicate()[0]

或者

>>> import subprocess
>>> p = subprocess.Popen(['ls', '-a'], stdout=subprocess.PIPE, 
...                                    stderr=subprocess.PIPE)
>>> out, err = p.communicate()
>>> print out
.
..
foo

如果您设置了stdin=PIPEcommunicate也允许您通过stdin向进程传递数据。
>>> cmd = ['awk', 'length($0) > 5']
>>> p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
...                           stderr=subprocess.PIPE,
...                           stdin=subprocess.PIPE)
>>> out, err = p.communicate('foo\nfoofoo\n')
>>> print out
foofoo

请注意 Aaron Hall的回答,它表明在某些系统上,您可能需要将stdoutstderrstdin全部设置为PIPE(或DEVNULL),才能使communicate正常工作。

在一些罕见情况下,您可能需要进行复杂的实时输出捕获。Vartec的回答提供了一种前进的方法,但是除communicate之外的其他方法如果使用不当可能会出现死锁。

与所有上述功能一样,当安全性不成问题时,可以通过传递shell=True来运行更复杂的shell命令。

注释

1. 运行shell命令:参数shell=True

通常,每次调用runcheck_outputPopen构造函数都会执行单个程序。这意味着没有花哨的bash风格的管道。如果要运行复杂的shell命令,可以传递shell=True,这三个函数都支持。例如:

>>> subprocess.check_output('cat books/* | wc', shell=True, text=True)
' 1299377 17005208 101299376\n'

然而,这样做会引发安全问题。如果你要做的不仅仅是轻量级脚本编程,最好分别调用每个进程,并将每个进程的输出作为下一个进程的输入传递,通过

run(cmd, [stdout=etc...], input=other_output)

或者

Popen(cmd, [stdout=etc...]).communicate(other_output)

直接连接管道的诱惑很强,要抵制它。否则,您可能会遇到死锁或不得不使用this等hacky方法。


8
使用check_output()communicate(),您必须等待进程完成,而使用poll(),则可以实时获取输出。这取决于您的需求。 - vartec
2
不确定这是否仅适用于较新版本的Python,但对我来说变量out的类型是<class 'bytes'>。为了将输出作为字符串获取,我必须在打印之前进行解码,如下所示: out.decode("utf-8") - PolyMesh
2
@Parsa 请参考subprocessshell=True的实际含义进行讨论。 - tripleee
1
@Khurshid 显而易见的快速解决方法是将shell=True与其一起运行,但更有效和优雅的解决方案是在Python中仅作为子进程运行ps并进行过滤。(如果您决定将其保留在shell中,您确实应该重构那些重复的grep。) - tripleee
1
谢谢您的回答,但我认为大多数人都在寻找 subprocess.check_output('cat books/* | wc', shell=True, text=True) 的功能,如果您能将其放在帖子顶部,那将非常有帮助。 - Digio
显示剩余11条评论

206

这种方法更加简单,但仅适用于Unix系统(包括Cygwin)和Python2.7。

import commands
print commands.getstatusoutput('wc -l file')
它返回一个包含(返回值、输出)的元组。
为了在Python2和Python3中都能正常工作,可以使用子进程(subprocess)模块来解决:
from subprocess import Popen, PIPE
output = Popen(["date"],stdout=PIPE)
response = output.communicate()
print response

34
现在已经被弃用,但对于没有subprocess.check_output的旧版本Python非常有用。 - static_rtti
27
请注意,此处涉及 Unix 特定内容,在 Windows 上可能会失败。 - Zitrax
4
+1 我必须使用古老的Python 2.4版本,这对我非常有帮助。 - WestCoastProjects

172

python3 提供了 subprocess.getoutput()

import subprocess
output = subprocess.getoutput("ls -l")
print(output)

9
它将命令的输出作为字符串返回,就这么简单。 - azhar22k
4
请注意,这明确标记为遗留功能,支持异常处理较差且没有安全保证。 - senderle
2
这与 subprocess.check_output 相比没有任何优势,除了少几个字符外,但考虑到缺点,这应该几乎不会影响你的决策。 - tripleee
嘿@PranavPatil,你在使用Linux吗?你能分享一下“which ls”命令的输出吗? - azhar22k
1
之后,将输出字符串分割成行可能会有所帮助:for line in output.splitlines(): print(line) 或者单词:for word in output.split(): print(word) - Celuk

128

类似于这样:

def runProcess(exe):    
    p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    while(True):
        # returns None while subprocess is running
        retcode = p.poll() 
        line = p.stdout.readline()
        yield line
        if retcode is not None:
            break

请注意,我正在将标准错误重定向到标准输出,这可能并不完全符合您的要求,但我希望能够获取错误消息。

此函数逐行生成输出(通常情况下,您需要等待子进程完成才能获取整个输出)。

对于您的情况,使用方法如下:

for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
    print line,

6
如果retcode是0,这是一个无限循环。应该检查if retcode is not None。不应该生成空字符串(即使是空行也至少有一个符号\n):if line: yield line。在结束时调用p.stdout.close() - jfs
1
顺便提一下,你可以直接从runProcess()返回iter(p.stdout.readline, b''),而无需使用while循环。 - jfs
2
我尝试了使用“ls -l /dirname”命令的代码,但在列出两个文件后就中断了,而实际上该目录中还有更多的文件。 - Vasilis
5
.readlines()方法会等到所有的输出被读取才返回,因此对于不能全部存入内存的大量输出它就会出问题。为了避免子进程退出后丢失缓冲数据,应该添加类似于if retcode is not None: yield from p.stdout.readlines(); break的代码。 - jfs
当进程完成时,您退出循环。难道此时标准输出仍未为空吗?(因为缓冲或其他原因)。 为什么不在readline返回空字节时退出? - lesnik
显示剩余5条评论

87

这是一个棘手但又非常简单的解决方案,适用于许多情况:

import os
os.system('sample_cmd > tmp')
print(open('tmp', 'r').read())

使用命令的输出创建一个临时文件(tmp),您可以从中读取所需的输出内容。

来自评论的额外说明:如果是一次性任务,您可以删除tmp文件。如果需要多次执行此操作,则无需删除tmp文件。

os.remove('tmp')

11
简陋但非常简单,适用于任何地方...可以与“mktemp”结合使用,使其在线程环境中运行。 - Prakash Rajagaopal
4
也许这是最快的方法,但最好加上 os.remove('tmp') 以使其“无文件”。 - XuMuK
@XuMuK 如果是一次性的工作,你是正确的。如果是重复性的工作,也许删除并不是必要的。 - Mehdi Saman Booy
4
对于并发性不利,对于可重入函数不利,对于不将系统恢复到启动前的状态不利(没有清理)。 - 2mia
1
@2mia 显然这很容易就能理解!如果你想将文件用作并发读写的共享内存,那么这不是一个好选择。但是,对于像拥有命令输出(例如ls或find等)的情况,它可能是一个不错且快速的选择。顺便说一下,如果您需要为简单问题提供快速解决方案,我认为这是最好的选择。如果您需要一个管道,则子进程可以更有效地为您工作。 - Mehdi Saman Booy
显示剩余3条评论

77

Vartec的答案没有读取所有行,所以我做了一个版本来实现:

def run_command(command):
    p = subprocess.Popen(command,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT)
    return iter(p.stdout.readline, b'')

使用方法与被接受的答案相同:

command = 'mysqladmin create test -uroot -pmysqladmin12'.split()
for line in run_command(command):
    print(line)

7
你可以使用 return iter(p.stdout.readline, b'') 替代 while 循环。 - jfs
2
这是一个相当酷的iter使用方式,我之前不知道!我已经更新了代码。 - Max Ekman
我已经删除了误导性的评论。我可以确认,即使子进程已经退出(p.poll() 不是 None),p.stdout.readline() 可能会返回先前缓冲的非空输出。 - jfs
1
这段代码不起作用。请参见 http://stackoverflow.com/questions/24340877/why-does-this-bash-call-from-python-not-work - thang
默认情况下shell=False,因此您应将命令作为列表传递(而不是字符串)。我已更新答案以添加缺失的.split() - jfs
显示剩余2条评论

30

您可以使用以下命令来运行任何Shell命令。我已经在Ubuntu上使用过它们。

import os
os.popen('your command here').read()

注意:自 Python 2.6 起,此功能已被弃用。现在您必须使用 subprocess.Popen。以下是示例:

import subprocess

p = subprocess.Popen("Your command", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
print p.split("\n")

3
自2.6版本起被弃用 - https://docs.python.org/2/library/os.html#os.popen - Filippo Vitale
1
@FilippoVitale 谢谢。我不知道它已经被弃用了。 - Muhammad Hassan
5
根据https://raspberrypi.stackexchange.com/questions/71547/is-there-a-problem-with-using-deprecated-os-popen/,在Python 2.6中,'os.popen()'已被弃用,但在Python 3.x中它并没有被弃用,因为在3.x中使用'subprocess.Popen()'实现了它。 - J-L
但是对于subprocess.check_output和类似的工具可以处理的简单任务,您也要避免使用subprocess.Popen。后者在处理非平凡命令时存在多个漏洞。 - tripleee
print 不是一个命令。你是不是想说 print() - rokejulianlockhart
显示剩余3条评论

14

对于Python 3.7+,使用subprocess.run并传递capture_output=True


import subprocess
result = subprocess.run(['echo', 'hello', 'world'], capture_output=True)
print(repr(result.stdout))

这将返回字节:

b'hello world\n'
如果您希望将字节转换为字符串,请加上text=True
result = subprocess.run(['echo', 'hello', 'world'], capture_output=True, text=True)
print(repr(result.stdout))

这将使用您的默认编码读取字节:

'hello world\n'

如果您需要手动指定不同的编码,请使用encoding="your encoding"而不是text=True

result = subprocess.run(['echo', 'hello', 'world'], capture_output=True, encoding="utf8")
print(repr(result.stdout))

13

我遇到了一个稍微不同的问题,需要满足以下要求:

  1. 实时捕获并返回STDOUT缓冲区中积累的消息。
    • @vartec通过使用生成器和上面的“yield”关键字巧妙地解决了这个问题。
  2. 打印所有STDOUT行(即使进程在完全读取STDOUT缓冲区之前退出)
  3. 不要浪费CPU周期以高频率轮询进程
  4. 检查子进程的返回代码
  5. 如果我们得到非零错误返回代码,则打印STDERR(与STDOUT分开)。

我已经结合和调整之前的答案,得出了以下结果:

import subprocess
from time import sleep

def run_command(command):
    p = subprocess.Popen(command,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         shell=True)
    # Read stdout from subprocess until the buffer is empty !
    for line in iter(p.stdout.readline, b''):
        if line: # Don't print blank lines
            yield line
    # This ensures the process has completed, AND sets the 'returncode' attr
    while p.poll() is None:                                                                                                                                        
        sleep(.1) #Don't waste CPU-cycles
    # Empty STDERR buffer
    err = p.stderr.read()
    if p.returncode != 0:
       # The run_command() function is responsible for logging STDERR 
       print("Error: " + str(err))

这段代码与之前的答案执行方式相同:

for line in run_command(cmd):
    print(line)

2
你介意解释一下加入sleep(.1)不会浪费CPU周期的原因吗? - Moataz Elmasry
2
如果我们在调用p.poll()之间没有任何休眠,那么我们将浪费数百万次调用此函数的CPU周期。相反,我们通过告诉操作系统我们在接下来的1/10秒内不需要被打扰,以便它可以执行其他任务来“节流”我们的循环。(可能p.poll()也会睡眠,使我们的睡眠语句变得多余)。 - The Aelfinn

12

你的结果可能会有所不同,我尝试了 @senderle 对 Vartec 的解决方法在 Python 2.6.5 上的 Windows 版本,但是我一直在遇到错误,并且没有其他解决方案可行。我的错误是:WindowsError: [Error 6] The handle is invalid

我发现我必须为每个句柄分配 PIPE 才能得到我期望的输出 - 以下是对我有效的方法。

import subprocess

def run_command(cmd):
    """given shell command, returns communication tuple of stdout and stderr"""
    return subprocess.Popen(cmd, 
                            stdout=subprocess.PIPE, 
                            stderr=subprocess.PIPE, 
                            stdin=subprocess.PIPE).communicate()

然后就可以像这样调用,([0] 获取元组的第一个元素,stdout):

run_command('tracert 11.1.0.1')[0]

经过更多学习,我相信我需要这些管道参数,因为我正在使用不同的句柄的自定义系统,所以我必须直接控制所有的std。

要停止控制台弹出窗口(在Windows下),请执行以下操作:

def run_command(cmd):
    """given shell command, returns communication tuple of stdout and stderr"""
    # instantiate a startupinfo obj:
    startupinfo = subprocess.STARTUPINFO()
    # set the use show window flag, might make conditional on being in Windows:
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    # pass as the startupinfo keyword argument:
    return subprocess.Popen(cmd,
                            stdout=subprocess.PIPE, 
                            stderr=subprocess.PIPE, 
                            stdin=subprocess.PIPE, 
                            startupinfo=startupinfo).communicate()

run_command('tracert 11.1.0.1')

1
有趣的是,这一定是Windows的问题。我会添加一个注释,以防其他人遇到类似的错误。 - senderle
1
如果您不从管道中读取/写入数据,那么请使用DEVNULL代替subprocess.PIPE,否则可能会挂起子进程。 - jfs

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