我想编写一个函数,该函数将执行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'

当前回答

对于相同的问题,我有一个稍微不同的需求:

捕获并返回STDOUT消息,因为它们在STDOUT缓冲区中累积(即实时)。 @vartec用python方法解决了这个问题,他使用生成器和“yield” 上面的字 打印所有STDOUT行(即使进程在STDOUT缓冲区可以完全读取之前退出) 不要浪费CPU周期以高频轮询进程 检查子流程的返回代码 如果得到非零错误返回码,则打印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)

其他回答

就像这样:

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

注意,我将stderr重定向到stdout,这可能不是你想要的,但我也想要错误消息。

这个函数一行一行地输出(通常你必须等待子进程完成才能得到完整的输出)。

对于你的情况,用法是:

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

对于相同的问题,我有一个稍微不同的需求:

捕获并返回STDOUT消息,因为它们在STDOUT缓冲区中累积(即实时)。 @vartec用python方法解决了这个问题,他使用生成器和“yield” 上面的字 打印所有STDOUT行(即使进程在STDOUT缓冲区可以完全读取之前退出) 不要浪费CPU周期以高频轮询进程 检查子流程的返回代码 如果得到非零错误返回码,则打印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)

在所有官方维护的Python版本中,最简单的方法是使用子进程。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写入输入,请跳过运行或Popen部分。如果您想执行复杂的shell命令,请参阅答案末尾shell=True的说明。

check_output函数适用于所有官方维护的Python版本。但是对于最近的版本,可以使用更灵活的方法。

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

如果您使用的是Python 3.5+,并且不需要向后兼容,那么对于大多数任务,官方文档都推荐使用新的run函数。它为子流程模块提供了一个非常通用的高级API。要捕获程序的输出,请传递子流程。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对象,所以如果想要一个合适的字符串,就需要解码它。假设被调用进程返回一个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。捕获结果。标准输出和常规输出)。如果希望在流程返回非零退出码时运行抛出异常,可以传递check=True。(或者你可以检查上面result的returncode属性。)当安全性不是问题时,您还可以通过传递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=True和stdout=PIPE的run,并且只返回stdout属性。

你可以传递stderr=subprocess。STDOUT以确保返回的输出中包含错误消息。当安全性不是问题时,您还可以通过传递shell=True来运行更复杂的shell命令,如答案末尾所述。

如果您需要从stderr管道或将输入传递给流程,check_output将无法完成该任务。在这种情况下,请参阅下面的Popen示例。

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

如果需要深度向后兼容性,或者需要比check_output或运行provider更复杂的功能,则必须直接使用Popen对象,它为子进程封装了低级API。

Popen构造函数可以接受不带参数的单个命令,也可以接受包含命令作为第一项的列表,后面跟着任意数量的参数,每个参数作为列表中的单独项。shlex。Split可以帮助将字符串解析为适当格式化的列表。Popen对象还接受许多用于进程IO管理和低级配置的不同参数。

要发送输入和捕获输出,通信几乎总是首选的方法。如:

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

Or

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

如果你设置了stdin=PIPE, communication也允许你通过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)才能使通信正常工作。

在极少数情况下,您可能需要复杂的实时输出捕获。Vartec的回答提出了一条前进的道路,但如果不小心使用,除了沟通之外的其他方法很容易陷入僵局。

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

笔记

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

通常,对run、check_output或Popen构造函数的每次调用都执行一个程序。这意味着没有花哨的敲击式管道。如果你想运行复杂的shell命令,你可以传递shell=True,这三个函数都支持。例如:

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

然而,这样做会引起安全问题。如果您要做的不是简单的脚本,那么最好分别调用每个进程,并将每个进程的输出作为输入传递给下一个进程,即via

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

Or

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

直接连接管道的诱惑很强烈;抵制它。否则,您可能会看到死锁,或者不得不做类似这样的讨厌的事情。

下面是一个简单而灵活的解决方案,适用于各种操作系统版本,以及Python 2和3,在shell模式下使用IPython:

from IPython.terminal.embed import InteractiveShellEmbed
my_shell = InteractiveShellEmbed()
result = my_shell.getoutput("echo hello world")
print(result)

Out: ['hello world']

它有几个优点

它只需要安装IPython,所以在使用它时,你真的不需要担心特定的Python或操作系统版本,它附带Jupyter -具有广泛的支持 它默认接受一个简单的字符串-所以不需要使用shell模式参数或字符串拆分,使它在我看来稍微干净一些 它还使替换变量甚至字符串本身的整个Python命令更加干净


为了演示:

var = "hello world "
result = my_shell.getoutput("echo {var*2}")
print(result)

Out: ['hello world hello world']

只是想给你一个额外的选择,特别是如果你已经安装了Jupyter

当然,如果你在一个实际的Jupyter笔记本,而不是一个.py脚本,你也可以总是这样做:

result = !echo hello world
print(result)

为了达到同样的目的。

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)