关于python:运行shell命令并捕获输出

Running shell command and capturing the output

我想编写一个函数,该函数将执行一个shell命令并将其输出作为字符串返回,不管它是错误消息还是成功消息。我只想得到和命令行相同的结果。

什么样的代码示例可以做到这一点?

例如:

1
2
3
4
5
6
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版本。最简单的方法是使用subprocess.check_output函数:好的。

1
2
3
4
>>> subprocess.check_output(['ls', '-l'])
b'total 0
-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files
'

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

check_output函数适用于几乎所有仍在广泛使用(2.7+)的python版本。2但对于较新版本,它不再是推荐的方法。好的。python的现代版本(3.5或更高版本):run

如果您使用的是python 3.5或更高版本,并且不需要向后兼容,建议使用新的run函数。它为subprocess模块提供了一个非常通用的高级API。要捕获程序的输出,请将subprocess.PIPE标志传递给stdout关键字参数。然后访问返回的CompletedProcess对象的stdout属性:好的。

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

返回值是一个bytes对象,因此如果需要一个适当的字符串,就需要decode对象。假设调用的进程返回一个utf-8编码的字符串:好的。

1
2
3
4
>>> result.stdout.decode('utf-8')
'total 0
-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files
'

所有这些都可以压缩为一个内衬:好的。

1
2
3
4
>>> subprocess.run(['ls', '-l'], stdout=subprocess.PIPE).stdout.decode('utf-8')
'total 0
-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files
'

如果要将输入传递给进程的stdin,请将bytes对象传递给input关键字参数:好的。

1
2
3
4
5
6
7
8
>>> cmd = ['awk', 'length($0) > 5']
>>> input = 'foo
foofoo
'
.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=input)
>>> result.stdout.decode('utf-8')
'foofoo
'

您可以通过传递stderr=subprocess.PIPE(捕获到result.stderrstderr=subprocess.STDOUT(捕获到result.stdout和常规输出)来捕获错误。当安全性不是问题时,您还可以通过传递shell=True来运行更复杂的shell命令,如下面的注释所述。好的。

与以前的做事方式相比,这只增加了一点复杂性。但我认为这是值得的回报:现在你可以做几乎任何你需要做的事情与run功能单独。好的。python的旧版本(2.7-3.4):check_output

如果您使用的是旧版本的python,或者需要适度的向后兼容性,那么您可能可以像上面简要描述的那样使用check_output函数。它从python 2.7开始就有了。好的。

1
subprocess.check_output(*popenargs, **kwargs)

它采用与Popen相同的参数(见下文),并返回包含程序输出的字符串。这个答案的开头有一个更详细的用法示例。在python 3.5及更高版本中,check_output相当于使用check=Truestdout=PIPE执行run,只返回stdout属性。好的。

可以传递stderr=subprocess.STDOUT,以确保返回的输出中包含错误消息,但在某些版本的python中,将stderr=subprocess.PIPE传递给check_output会导致死锁。当安全性不是问题时,您还可以通过传递shell=True来运行更复杂的shell命令,如下面的注释所述。好的。

如果需要从stderr发送管道或将输入传递到进程,check_output将无法完成任务。在这种情况下,请参阅下面的Popen示例。好的。复杂的应用程序和python(2.6及以下)的旧版本:Popen

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

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

为了发送输入和捕获输出,communicate几乎总是首选的方法。如:好的。

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

或好的。

1
2
3
4
5
6
7
8
>>> import subprocess
>>> p = subprocess.Popen(['ls', '-a'], stdout=subprocess.PIPE,
...                                    stderr=subprocess.PIPE)
>>> out, err = p.communicate()
>>> print out
.
..
foo

如果设置stdin=PIPEcommunicate还允许您通过stdin向进程传递数据:好的。

1
2
3
4
5
6
7
8
9
>>> cmd = ['awk', 'length($0) > 5']
>>> p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
...                           stderr=subprocess.PIPE,
...                           stdin=subprocess.PIPE)
>>> out, err = p.communicate('foo
foofoo
'
)
>>> print out
foofoo

注意Aaron Hall的回答,这表明在某些系统上,您可能需要将stdoutstderrstdin全部设置为PIPEDEVNULL,以使communicate完全工作。好的。

在一些罕见的情况下,您可能需要复杂的实时输出捕获。Vartec的回答表明了一条前进的道路,但是除communicate之外的方法如果不小心使用,很容易出现死锁。好的。

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

1。运行shell命令:shell=True参数好的。

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

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

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

或好的。

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

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

2。Unicode注意事项好的。

check_output返回python 2中的字符串,但python 3中的bytes对象。如果你还没有学会Unicode,那就花点时间学习一下吧。好的。好啊。


这很容易,但只在Unix(包括Cygwin)上工作。

1
2
import commands
print commands.getstatusoutput('wc -l file')

它返回一个具有(返回值,输出)的元组

这只在python2.7中有效:在python3上不可用。对于同时在这两种模式下工作的解决方案,请使用subprocess模块:

1
2
3
4
import subprocess
output=subprocess.Popen(["date"],stdout=PIPE)
response=output.communicate()
print response


就像这样:

1
2
3
4
5
6
7
8
9
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,这可能不是您想要的,但我也需要错误消息。

这个函数在一行一行地生成它们(通常您需要等待子进程完成才能获得整个输出)。

对于您的案例,用法是:

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


Vartec的答案并不是所有的行,所以我做了一个版本:

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

用法与接受的答案相同:

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


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

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

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

评论中的额外说明:对于一次性作业,可以删除tmp文件。如果需要多次执行此操作,则无需删除tmp。

1
os.remove('tmp')


在Python 3.5中:

1
2
3
4
5
import subprocess

output = subprocess.run("ls -l", shell=True, stdout=subprocess.PIPE,
                        universal_newlines=True)
print(output.stdout)


我也有同样的问题但找到了一个非常简单的方法遵循这一点

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

希望能有所帮助

注:该溶液为python3专用溶液,因为subprocess.getoutput()在python2中不起作用。


可以使用以下命令运行任何shell命令。我在Ubuntu上用过。

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


现代python解决方案(>=3.1):

1
 res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)


您的里程数可能会有所不同,我在python 2.6.5上尝试了@senderle对vartec在windows中的解决方案进行旋转,但我遇到了错误,没有其他解决方案起作用。我的错误是:WindowsError: [Error 6] The handle is invalid

我发现我必须为每个句柄分配pipe,以使其返回我期望的输出——以下内容对我很有用。

1
2
3
4
5
6
7
8
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)

1
run_command('tracert 11.1.0.1')[0]

在了解了更多之后,我相信我需要这些管道参数,因为我正在使用不同的句柄的定制系统上工作,所以我必须直接控制所有的STD。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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')


我对同样的问题有一个稍微不同的口味,有以下要求:

  • 捕获并返回在stdout缓冲区中累积的stdout消息(即实时)。
    • @瓦特克用发电机和"产量"解决了这个难题。关键字以上
  • 打印所有stdout行(即使进程在stdout缓冲区完全读取之前退出)
  • 不要浪费CPU周期以高频率轮询进程
  • 检查子进程的返回代码
  • 如果得到非零错误返回代码,则打印stderr(与stdout分开)。
  • 我结合并调整了以前的答案,得出以下结论:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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))

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

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


    如果你需要在多个文件上运行shell命令,这对我来说就是一个技巧。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import os
    import subprocess

    # Define a function for running commands and capturing stdout line by line
    # (Modified from Vartec's solution because it wasn't printing all lines)
    def runProcess(exe):    
        p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        return iter(p.stdout.readline, b'')

    # Get all filenames in working directory
    for filename in os.listdir('./'):
        # This command will be run on each file
        cmd = 'nm ' + filename

        # Run the command and capture the output line by line.
        for line in runProcess(cmd.split()):
            # Eliminate leading and trailing whitespace
            line.strip()
            # Split the output
            output = line.split()

            # Filter the output and print relevant lines
            if len(output) > 2:
                if ((output[2] == 'set_program_name')):
                    print filename
                    print line

    编辑:刚刚看到马克斯·佩尔松的解决方案和J.F.塞巴斯蒂安的建议。继续进行合并。


    分割subprocess的初始命令可能会很麻烦。

    shlex.split帮助自己。

    样本命令

    git log -n 5 --since"5 years ago" --until"2 year ago"

    代码

    1
    2
    3
    4
    5
    6
    7
    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
    Author: Artur Barseghyan...'

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

  • 没有输出,但运行成功
  • 输出空行,运行成功
  • 运行失败
  • 输出某物,运行成功
  • 函数如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    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

    , Unix has
    , Old mac has
                # if line not in ['','
    ','
    ','

    ']: # 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""