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

当前回答

我在Python 2.6.5的Windows上尝试了@senderle对Vartec解决方案的旋转,但我得到了错误,没有其他解决方案有效。我的错误是:WindowsError:[错误6]句柄无效。

我发现我必须将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')

其他回答

改进以获得更好的日志记录。 为了获得更好的输出,可以使用迭代器。 从下面开始,我们变得更好

from subprocess import Popen, getstatusoutput, PIPE
def shell_command(cmd):
    result = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)

    output = iter(result.stdout.readline, b'')
    error = iter(result.stderr.readline, b'')
    print("##### OutPut ###")
    for line in output:
        print(line.decode("utf-8"))
    print("###### Error ########")
    for line in error:
        print(error.decode("utf-8")) # Convert bytes to str

    status, terminal_output = run_command(cmd)
    print(terminal_output)

shell_command("ls") # this will display all the files & folders in directory

使用getstatusoutput的其他方法(容易理解)

from subprocess import Popen, getstatusoutput, PIPE

status_Code, output = getstausoutput(command)
print(output) # this will give the terminal output

# status_code, output = getstatusoutput("ls") # this will print the all files & folder available in the directory

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

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

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)

就像这样:

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,

在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))