运行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'
25 个回答
python3
提供了一个叫做 subprocess.getoutput()
的功能:
import subprocess
output = subprocess.getoutput("ls -l")
print(output)
这个方法简单多了,但只适用于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
在所有官方维护的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
会运行一个只接受参数的程序。它返回的结果和在stdout
上打印的一模一样。如果你需要给stdin
写入输入,可以直接跳到run
或Popen
部分。如果你想执行复杂的shell命令,可以查看本回答最后关于shell=True
的说明。
check_output
函数在所有官方维护的Python版本中都可以使用。不过在更新的版本中,有一种更灵活的方法。
现代版本的Python(3.5及以上):run
如果你使用的是Python 3.5+,并且不需要向下兼容,官方文档推荐使用新的run
函数来完成大多数任务。它为subprocess
模块提供了一个非常通用的高级接口。要捕获程序的输出,可以将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
)来捕获错误。如果你希望run
在进程返回非零退出代码时抛出异常,可以传递check=True
。(或者你可以检查上面result
的returncode
属性。)当安全性不是问题时,你也可以通过传递shell=True
来运行更复杂的shell命令,具体说明在本回答的最后。
在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
等同于执行run
时传入check=True
和stdout=PIPE
,并仅返回stdout
属性。
你可以传递stderr=subprocess.STDOUT
来确保错误信息包含在返回的输出中。当安全性不是问题时,你也可以通过传递shell=True
来运行更复杂的shell命令,具体说明在本回答的最后。
如果你需要从stderr
中获取数据或给进程传入输入,check_output
就不够用了。那种情况下可以查看下面的Popen
示例。
复杂应用和旧版本的Python(2.6及以下):Popen
如果你需要很好的向下兼容性,或者需要比check_output
或run
提供的功能更复杂的功能,你就需要直接使用Popen
对象,它封装了子进程的低级API。
Popen
构造函数可以接受一个没有参数的单个命令,或者一个列表,列表的第一个元素是命令,后面是任意数量的参数,每个参数都是列表中的一个单独元素。shlex.split
可以帮助将字符串解析为适当格式的列表。Popen
对象还接受一系列不同的参数,用于进程输入输出管理和低级配置。
要发送输入并捕获输出,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=PIPE
,communicate
还允许你通过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的回答,它指出在某些系统上,你可能需要将stdout
、stderr
和stdin
都设置为PIPE
(或DEVNULL
),才能使communicate
正常工作。
在一些少见的情况下,你可能需要复杂的实时输出捕获。Vartec的回答提供了一种解决方案,但如果不小心使用,除了communicate
以外的方法容易导致死锁。
和上述所有函数一样,当安全性不是问题时,你可以通过传递shell=True
来运行更复杂的shell命令。
注意事项
1. 运行shell命令:shell=True
参数
通常,每次调用run
、check_output
或Popen
构造函数时,都是在执行一个单一程序。这意味着不能使用复杂的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)
直接连接管道的诱惑很强;要抵制它。否则,你可能会遇到死锁,或者不得不做一些像这样的黑客行为。