首页 文章

运行shell命令并捕获输出

提问于
浏览
649

我想编写一个函数来执行shell命令并返回其输出 as a string ,无论是错误还是成功消息 . 我只想获得与命令行相同的结果 .

什么是代码示例会做这样的事情?

例如:

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'

13 回答

  • 786

    在Python 3.5中:

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

    Vartec's答案没有读取所有行,所以我做了一个版本:

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

    您可以使用以下命令运行任何shell命令 . 我在ubuntu上使用过它们 .

    import os
    os.popen('your command here').read()
    
  • 41

    您的里程可能会变化,我在Windows 2.6.5上尝试了@senderle 's spin on Vartec'的解决方案,但我遇到了错误,没有其他解决方案有效 . 我的错误是: WindowsError: [Error 6] The handle is invalid .

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

    这个问题的答案取决于您使用的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 ,请跳至 runPopen 部分 . 如果要执行复杂的shell命令,请参阅本答案末尾的 shell=True 上的注释 .

    check_output 函数适用于几乎所有仍在广泛使用的Python版本(2.7).2但是对于更新版本,它不再是推荐的方法 .

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

    如果您使用 Python 3.5 或更高版本以及 do not need backwards compatibility ,则建议使用new run function . 它为 subprocess 模块提供了一个非常通用的高级API . 要捕获程序的输出,请将 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']
    >>> input = 'foo\nfoofoo\n'.encode('utf-8')
    >>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=input)
    >>> result.stdout.decode('utf-8')
    'foofoo\n'
    

    您可以通过传递 stderr=subprocess.PIPE (捕获到 result.stderr )或 stderr=subprocess.STDOUT (捕获到 result.stdout 以及常规输出)来捕获错误 . 如果不担心安全性,您还可以通过传递 shell=True 来运行更复杂的shell命令,如下面的注释所述 .

    与旧的做事方式相比,这增加了一点复杂性 . 但我认为这是值得的回报:现在你几乎可以做任何你需要做的事情 run 功能 .

    旧版本的Python(2.7-3.4):check_output

    如果您使用的是旧版本的Python,或者需要适度的向后兼容性,则可以使用上面简要描述的 check_output 函数 . 它自Python 2.7开始提供 .

    subprocess.check_output(*popenargs, **kwargs)
    

    它需要与 Popen (见下文)相同的参数,并返回包含程序输出的字符串 . 这个答案的开头有一个更详细的用法示例 .

    您可以传递 stderr=subprocess.STDOUT 以确保返回的输出中包含错误消息 - 但不要将 stderr=subprocess.PIPE 传递给 check_output . 它可能导致deadlocks . 如果不担心安全性,您还可以通过传递 shell=True 来运行更复杂的shell命令,如下面的注释所述 .

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

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

    如果您需要深度向后兼容性,或者如果您需要比 check_output 提供的更复杂的功能,则必须直接使用 Popen 对象,这些对象封装了子进程的低级API .

    Popen 构造函数接受不带参数的 a single command 或包含命令作为其第一项的 a list ,后跟任意数量的参数,每个参数作为列表中的单独项 . shlex.split可以帮助将字符串解析为格式正确的列表 . 对于进程IO管理和低级配置, Popen 对象也接受host of different arguments .

    要发送输入和捕获输出, 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=PIPEcommunicate 也允许您通过 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's answer,表示在某些系统上,您可能需要将 stdoutstderrstdin all设置为 PIPE (或 DEVNULL )让 communicate 完全工作 .

    在极少数情况下,您可能需要复杂的实时输出捕获 . Vartec的回答表明了前进的方向,但如果不仔细使用, communicate 以外的方法很容易出现死锁 .

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

    注意事项

    1. Running shell commands: the shell=True argument

    通常,每次调用 runcheck_outputPopen 构造函数都会执行一个程序 . 这意味着没有花哨的bash式管道 . 如果要运行复杂的shell命令,可以传递 shell=True ,这三个函数都支持 .

    但是,这样做会引发security concerns . 如果你做的不仅仅是轻量级脚本,你可能最好分别调用每个进程,并将每个进程的输出作为输入传递给下一个进程,通过

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

    要么

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

    直接连接管道的诱惑力很强;抵制它 . 否则,你可能会看到死锁或者必须做像this这样的hacky事情 .

    2. Unicode considerations

    check_output 在Python 2中返回一个字符串,但在Python 3中返回一个 bytes 对象 . 如果你还没有,那么值得花点时间learn about unicode .

  • 0

    这是一个 tricky 但是 super simple 解决方案,适用于许多情况:

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

    使用命令的输出创建临时文件(此处为tmp),您可以从中读取所需的输出 .

    注释中的额外注释:您可以在一次性作业的情况下删除tmp文件 . 如果您需要多次执行此操作,则无需删除tmp .

    os.remove('tmp')
    
  • 17

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

    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.塞巴斯蒂安的建议 . 走在前面,并将其纳入其中 .

  • 175

    这样更容易,但只适用于Unix(包括Cygwin) .

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

    它返回一个带有(return_value,output)的元组

    这仅适用于 python2.7 :它在 python3 上不可用 . 对于兼容两者的解决方案,请使用 subprocess 模块:

    import subprocess
    output=subprocess.Popen(["date"],stdout=PIPE)
    response=output.communicate()
    print response
    
  • 12

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

    res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)
    
  • 2

    像这样的东西:

    def runProcess(exe):    
        p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        while(True):
          retcode = p.poll() #returns None while subprocess is running
          line = p.stdout.readline()
          yield line
          if(retcode is not None):
            break
    

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

    这个函数 yields line by line as they come (通常你必须等待子进程完成以获得整个输出) .

    对于您的情况,用法将是:

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

    我遇到了同样的问题但是想出了一个非常简单的方法

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

    希望它有所帮助

    注意:此解决方案是特定于python3的,因为 subprocess.getoutput() 在python2中不起作用

  • 13

    I had a slightly different flavor of the same problem with the following requirements:

    • 捕获并返回STDOUT消息,因为它们在STDOUT缓冲区中累积(即实时) .

    • @vartec通过使用生成器和'yield'来解决这个问题 .
      以上关键字

    • 打印所有STDOUT行(即使在完全读取STDOUT缓冲区之前进程退出)

    • 不要浪费CPU周期以高频率轮询过程

    • 检查子进程的返回码

    • 如果我们得到非零错误返回码,则打印STDERR(与STDOUT分开) .

    I've combined and tweaked previous answers to come up with the following:

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

    This code would be executed the same as previous answers:

    for line in run_command(cmd):
        print(line)
    
  • 13

    例如,执行('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 ""
    

相关问题