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

当前回答

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)

其他回答

在所有官方维护的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)

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

拆分子进程的初始命令可能非常棘手和麻烦。

使用shlex.split()来帮助自己。

示例命令

Git log -n 5——5年前到2年前

的代码

from subprocess import check_output
from shlex import split

res = check_output(split('git log -n 5 --since "5 years ago" --until "2 year ago"'))
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'

如果没有shlex.split(),代码将如下所示

res = check_output([
    'git', 
    'log', 
    '-n', 
    '5', 
    '--since', 
    '5 years ago', 
    '--until', 
    '2 year ago'
])
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'

就像这样:

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,

例如,execute('ls -ahl') 区分三/四种可能的回报和操作系统平台:

没有输出,但运行成功 输出空行,运行成功 运行失败 输出一些东西,成功运行

下面的函数

def execute(cmd, output=True, DEBUG_MODE=False):
"""Executes a bash command.
(cmd, output=True)
output: whether print shell output to screen, only affects screen display, does not affect returned values
return: ...regardless of output=True/False...
        returns shell output as a list with each elment is a line of string (whitespace stripped both sides) from output
        could be 
        [], ie, len()=0 --> no output;    
        [''] --> output empty line;     
        None --> error occured, see below

        if error ocurs, returns None (ie, is None), print out the error message to screen
"""
if not DEBUG_MODE:
    print "Command: " + cmd

    # https://stackoverflow.com/a/40139101/2292993
    def _execute_cmd(cmd):
        if os.name == 'nt' or platform.system() == 'Windows':
            # set stdin, out, err all to PIPE to get results (other than None) after run the Popen() instance
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        else:
            # Use bash; the default is sh
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable="/bin/bash")

        # the Popen() instance starts running once instantiated (??)
        # additionally, communicate(), or poll() and wait process to terminate
        # communicate() accepts optional input as stdin to the pipe (requires setting stdin=subprocess.PIPE above), return out, err as tuple
        # if communicate(), the results are buffered in memory

        # Read stdout from subprocess until the buffer is empty !
        # if error occurs, the stdout is '', which means the below loop is essentially skipped
        # A prefix of 'b' or 'B' is ignored in Python 2; 
        # it indicates that the literal should become a bytes literal in Python 3 
        # (e.g. when code is automatically converted with 2to3).
        # return iter(p.stdout.readline, b'')
        for line in iter(p.stdout.readline, b''):
            # # Windows has \r\n, Unix has \n, Old mac has \r
            # if line not in ['','\n','\r','\r\n']: # Don't print blank lines
                yield line
        while p.poll() is None:                                                                                                                                        
            sleep(.1) #Don't waste CPU-cycles
        # Empty STDERR buffer
        err = p.stderr.read()
        if p.returncode != 0:
            # responsible for logging STDERR 
            print("Error: " + str(err))
            yield None

    out = []
    for line in _execute_cmd(cmd):
        # error did not occur earlier
        if line is not None:
            # trailing comma to avoid a newline (by print itself) being printed
            if output: print line,
            out.append(line.strip())
        else:
            # error occured earlier
            out = None
    return out
else:
    print "Simulation! The command is " + cmd
    print ""

出于某种原因,这个方法适用于Python 2.7,你只需要导入操作系统!

import os 

def bash(command):
    output = os.popen(command).read()
    return output

print_me = bash('ls -l')
print(print_me)