运行shell命令并捕获输出

Sil*_*ght 816 python shell subprocess

我想编写一个函数来执行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'
Run Code Online (Sandbox Code Playgroud)

sen*_*rle 1021

这个问题的答案取决于您正在使用的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'
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

>>> 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'
Run Code Online (Sandbox Code Playgroud)

返回值是一个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'
Run Code Online (Sandbox Code Playgroud)

这可以全部压缩成一行:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

如果要将输入传递给进程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'
Run Code Online (Sandbox Code Playgroud)

您可以通过传递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)  
Run Code Online (Sandbox Code Playgroud)

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

您可以传递check_output以确保返回的输出中包含错误消息 - 但不传递runcheck=True.它可能导致死锁.如果不担心安全性,您还可以stdout=PIPE按照下面的说明中的说明运行更复杂的shell命令.

如果您需要管道stdout输入或将输入传递给流程,stderr=subprocess.STDOUT则无法完成任务.stderr=subprocess.PIPE在这种情况下,请参阅下面的示例.

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

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

所述check_output构造器接受单个命令没有参数,或列表包含指令作为其第一项,其次是任意数量的参数,每个作为列表一个单独的项目.Popen可以帮助将字符串解析为适当格式化的列表.Popen对象还接受一系列不同的参数,用于进程IO管理和低级配置.

要发送输入和捕获输出,check_output几乎总是首选方法.如:

output = subprocess.Popen(["mycmd", "myarg"], 
                          stdout=subprocess.PIPE).communicate()[0]
Run Code Online (Sandbox Code Playgroud)

要么

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

如果设置Popen,Popen还允许您通过以下方式将数据传递给流程shlex.split:

>>> 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
Run Code Online (Sandbox Code Playgroud)

注意艾伦·霍尔的回答,这表明在某些系统上,你可能需要设置Popen,communicate以及stdin=PIPE所有communicate(或stdin)来获得stdout在所有的工作.

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

与上述所有功能一样,当安全性不是问题时,您可以通过传递来运行更复杂的shell命令stdin.

笔记

1.运行shell命令:PIPE参数

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

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

run(cmd, [stdout=etc...], input=other_output)
Run Code Online (Sandbox Code Playgroud)

要么

Popen(cmd, [stdout=etc...]).communicate(other_output)
Run Code Online (Sandbox Code Playgroud)

直接连接管道的诱惑力很强; 抵制它.否则,你可能会看到死锁或有做这样的事情哈克这样.

2. Unicode考虑因素

shell=True在Python 2中返回一个字符串,但run在Python 3中返回一个对象.如果你还没有,那么值得花一点时间来学习unicode.

  • 使用`check_output()`和`communic()`你必须等到进程完成,`poll()`你得到输出.真的取决于你需要什么. (4认同)
  • 不确定这是否仅适用于更高版本的Python,但变量`out`对我来说是类型`<class'bytes'>`.为了将输出作为字符串,我必须在打印前解码它,如:`out.decode("utf-8")` (2认同)
  • @Parsa请参阅[`subprocess`中`shell=True`的实际含义](/sf/ask/222072931/)进行讨论。 (2认同)

byt*_*ray 183

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

import commands
print commands.getstatusoutput('wc -l file')
Run Code Online (Sandbox Code Playgroud)

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

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

from subprocess import Popen, PIPE
output = Popen(["date"],stdout=PIPE)
response = output.communicate()
print response
Run Code Online (Sandbox Code Playgroud)

  • 现在已弃用,但对于没有subprocess.check_output的旧python版本非常有用 (29认同)
  • 请注意,这是特定于Unix的.例如,它将在Windows上失败. (21认同)
  • +1我必须处理古老版本的python 2.4,这非常有帮助 (4认同)

var*_*tec 105

像这样的东西:

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
Run Code Online (Sandbox Code Playgroud)

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

这个函数在它们到来时逐行产生(通常你必须等待子进程完成以获得整个输出).

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

for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
    print line,
Run Code Online (Sandbox Code Playgroud)

  • -1:如果`retcode`为'0`,则为无限循环.检查应该是"如果retcode不是None".你不应该产生空字符串(即使空行至少有一个符号'\n'):`if line:yield line`.最后调用`p.stdout.close()`. (4认同)
  • @fuenfundachtzig:`.readlines()`在读取*all*输出之前不会返回,因此它会因为不适合内存的大输出而中断.为了避免在子进程退出后丢失缓冲数据,应该有一个类似的"如果retcode不是None:从p.stdout.readlines()得到; break` (3认同)
  • 我尝试使用ls -l/dirname的代码,并在列出两个文件后中断,而目录中有更多的文件 (2认同)

Max*_*man 63

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

def run_command(command):
    p = subprocess.Popen(command,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT)
    return iter(p.stdout.readline, b'')
Run Code Online (Sandbox Code Playgroud)

用法与接受的答案相同:

command = 'mysqladmin create test -uroot -pmysqladmin12'.split()
for line in run_command(command):
    print(line)
Run Code Online (Sandbox Code Playgroud)

  • 你可以使用`return iter(p.stdout.readline,b'')`而不是while循环 (6认同)
  • 这是一个非常酷的 iter 用法,不知道!我更新了代码。 (2认同)

Meh*_*902 50

这是一个棘手超级简单的解决方案,适用于许多情况:

import os
os.system('sample_cmd > tmp')
print open('tmp', 'r').read()
Run Code Online (Sandbox Code Playgroud)

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

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

os.remove('tmp')
Run Code Online (Sandbox Code Playgroud)

  • Hacky但是超级简单+可以在任何地方工作..可以将它与`mktemp`结合起来,使其在线程情况下工作我猜 (3认同)
  • 也许是最快的方法,但最好添加`os.remove('tmp')`以使其"无文件". (2认同)
  • 不利于并发,不利于可重入函数,不利于没有像启动前那样离开系统(没有清理) (2认同)

azh*_*22k 27

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

import subprocess
output = subprocess.getoutput("ls -l")
print(output)
Run Code Online (Sandbox Code Playgroud)

希望它有所帮助

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

  • 对于大多数用例,这就是人们可能想要的:易于记忆,不必解码结果等。谢谢。 (5认同)
  • 请注意,这明确标记为[遗留函数](https://docs.python.org/3/library/subprocess.html#legacy-shell-invocation-functions),对异常处理的支持很差并且没有安全保证。 (4认同)
  • 它以字符串形式返回命令的输出,就这么简单 (3认同)
  • @Dev print(s)是有效的python 2. subprocess.getoutput不是. (2认同)
  • 除了短几个字符之外,这比“subprocess.check_output”没有任何好处,但考虑到缺点,这应该不会影响你。 (2认同)

小智 16

Python 3.7+上,使用subprocess.run并传递capture_output=True

import subprocess
result = subprocess.run(['echo', 'hello', 'world'], capture_output=True)
print(repr(result.stdout))
Run Code Online (Sandbox Code Playgroud)

这将返回字节:

b'hello world\n'
Run Code Online (Sandbox Code Playgroud)

如果您希望它将字节转换为字符串,请添加text=True

result = subprocess.run(['echo', 'hello', 'world'], capture_output=True, text=True)
print(repr(result.stdout))
Run Code Online (Sandbox Code Playgroud)

这将使用默认编码读取字节:

'hello world\n'
Run Code Online (Sandbox Code Playgroud)

如果您需要手动指定不同的编码,请encoding="your encoding"使用text=True

result = subprocess.run(['echo', 'hello', 'world'], capture_output=True, encoding="utf8")
print(repr(result.stdout))
Run Code Online (Sandbox Code Playgroud)


Muh*_*san 15

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

import os
os.popen('your command here').read()
Run Code Online (Sandbox Code Playgroud)

  • 根据 https://raspberrypi.stackexchange.com/questions/71547/is-there-a-problem-with-using-deprecated-os-popen `os.popen()` 在 Python 2.6 中已弃用,但它是 *在 Python 3.x 中不*弃用,因为在 3.x 中它是使用 `subprocess.Popen()` 实现的。 (5认同)
  • 自2.6版起弃用– https://docs.python.org/2/library/os.html#os.popen (2认同)

Aar*_*all 11

你的里程可能会变化,我试图在@ 2.6中使用@remitle在Windows上的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()
Run Code Online (Sandbox Code Playgroud)

并像这样调用,([0]获取元组的第一个元素stdout):

run_command('tracert 11.1.0.1')[0]
Run Code Online (Sandbox Code Playgroud)

在了解更多之后,我相信我需要这些管道参数,因为我正在使用不同句柄的自定义系统,所以我必须直接控制所有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')
Run Code Online (Sandbox Code Playgroud)


The*_*inn 9

我对以下要求的问题略有不同:

  1. 捕获并返回STDOUT消息,因为它们在STDOUT缓冲区中累积(即实时).
    • @vartec通过使用生成器和
      上面的'yield' 关键字来解决这个问题
  2. 打印所有STDOUT行(即使在完全读取STDOUT缓冲区之前进程退出)
  3. 不要浪费CPU周期以高频率轮询过程
  4. 检查子进程的返回码
  5. 如果我们得到非零错误返回码,则打印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))
Run Code Online (Sandbox Code Playgroud)

此代码将与先前的答案执行相同:

for line in run_command(cmd):
    print(line)
Run Code Online (Sandbox Code Playgroud)

  • 您介意解释一下添加 sleep(.1) 不会浪费 CPU 周期吗? (2认同)
  • 如果我们继续调用``p.poll()``而在两次调用之间没有任何睡眠,那么通过数百万次调用此函数将浪费CPU周期。取而代之的是,我们通过告诉操作系统在接下来的1/10秒内无需打扰,从而“限制”了循环,以便它可以执行其他任务。(p.poll()也可能会休眠,从而使我们的sleep语句变得多余)。 (2认同)

Art*_*yan 8

拆分初始命令subprocess可能是棘手和麻烦的。

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

示例命令

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

编码

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...'
Run Code Online (Sandbox Code Playgroud)

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

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...'
Run Code Online (Sandbox Code Playgroud)

  • `shlex.split()` 很方便,特别是如果您不知道 shell 中的引用到底是如何工作的;但手动将此字符串转换为列表 `['git', 'log', '-n', '5', '--since', '5 年前', '--until', '2 年前' ]` 如果你理解引用的话,一点也不难。 (2认同)

Joy*_*ama 6

这是一个解决方案,如果您想在进程运行时打印输出,则可以工作。


我还添加了当前工作目录,这对我不止一次有用。


希望该解决方案能帮助某人:)。

import subprocess

def run_command(cmd_and_args, print_constantly=False, cwd=None):
"""Runs a system command.

:param cmd_and_args: the command to run with or without a Pipe (|).
:param print_constantly: If True then the output is logged in continuous until the command ended.
:param cwd: the current working directory (the directory from which you will like to execute the command)
:return: - a tuple containing the return code, the stdout and the stderr of the command
"""
output = []

process = subprocess.Popen(cmd_and_args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)

while True:
    next_line = process.stdout.readline()
    if next_line:
        output.append(str(next_line))
        if print_constantly:
            print(next_line)
    elif not process.poll():
        break

error = process.communicate()[1]

return process.returncode, '\n'.join(output), error
Run Code Online (Sandbox Code Playgroud)


Geo*_*oub 6

由于某种原因,这个可以在 Python 2.7 上运行,你只需要导入 os!

import os 

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

print_me = bash('ls -l')
print(print_me)
Run Code Online (Sandbox Code Playgroud)


Eth*_*der 5

如果您需要在多个文件上运行 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
Run Code Online (Sandbox Code Playgroud)

编辑:刚刚根据 JF Sebastian 的建议看到了 Max Persson 的解决方案。继续前进并将其纳入其中。


Neo*_* li 5

根据@senderle,如果你像我一样使用 python3.6:

def sh(cmd, input=""):
    rst = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, input=input.encode("utf-8"))
    assert rst.returncode == 0, rst.stderr.decode("utf-8")
    return rst.stdout.decode("utf-8")
Run Code Online (Sandbox Code Playgroud)
sh("ls -a")
Run Code Online (Sandbox Code Playgroud)

其行为与您在 bash 中运行命令完全相同

  • 您正在重新发明关键字参数 `check=True, universal_newlines=True`。换句话说,“subprocess.run()”已经完成了代码所做的一切。 (2认同)

归档时间:

查看次数:

957707 次

最近记录:

6 年,1 月 前