如何执行程序或调用系统命令?

6066

如何在Python中调用外部命令,就像在shell或命令提示符中键入它一样?

66个回答

84

还要检查一下Python库"pexpect"。

它可以交互式地控制外部程序/命令,甚至包括ssh、ftp、telnet等。你只需要输入类似于:

child = pexpect.spawn('ftp 192.168.0.24')

child.expect('(?i)name .*: ')

child.sendline('anonymous')

child.expect('(?i)password')

83
如果你需要调用命令并获得输出结果,可以使用subprocess.check_output(Python 2.7+)。
>>> subprocess.check_output(["ls", "-l", "/dev/null"])
'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'

还要注意shell参数。

如果shell为True,则指定的命令将通过shell执行。如果您主要使用Python来增强大多数系统shell提供的控制流,并且仍然希望方便地访问其他shell功能(例如shell管道,文件名通配符,环境变量扩展和将~扩展到用户的主目录),则这可能非常有用。但是,请注意,Python本身提供了许多类似于shell的功能实现(特别是glob,fnmatch,os.walk(),os.path.expandvars(),os.path.expanduser()和shutil)。


2
请注意,check_output 需要一个列表而不是一个字符串。如果您不依赖于引号空格来使您的调用有效,则最简单、最易读的方法是 subprocess.check_output("ls -l /dev/null".split()) - Bruno Bronosky
就像答案含糊地提到的那样,以及本页上的许多其他答案更详细地解释的那样,您可以传递一个列表,或者使用 shell=True 传递一个单个字符串,然后 shell 将负责解析和执行。在您提到的情况下使用纯 .split() 是可以的,但是初学者通常不理解细微之处;您可能最好推荐 shlex.split(),它确实正确处理引号和反斜杠转义。 - tripleee

69

更新:

subprocess.run 是建议使用的方法(自Python 3.5起),如果您的代码不需要与早期版本的Python保持兼容性。它更一致,并且提供类似Envoy的易用性。(尽管管道不是那么简单。请参见此问题以了解详情。)

以下是文档中的一些示例。

运行进程:

>>> subprocess.run(["ls", "-l"])  # Doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

在运行失败时抛出异常:
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

捕获输出:

>>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')

原始回答:

我建议尝试 Envoy。它是 subprocess 的包装器,而 subprocess 又旨在取代较旧的模块和函数。Envoy 是面向人类的 subprocess。

来自README的示例用法:

>>> r = envoy.run('git config', data='data to pipe in', timeout=2)

>>> r.status_code
129
>>> r.std_out
'usage: git config [options]'
>>> r.std_err
''

将东西传递到管道中:
>>> r = envoy.run('uptime | pbcopy')

>>> r.command
'pbcopy'
>>> r.status_code
0

>>> r.history
[<Response 'uptime'>]

66

这就是我运行命令的方式。 这段代码基本上包含了您所需的一切。

from subprocess import Popen, PIPE
cmd = "ls -l ~/"
p = Popen(cmd , shell=True, stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
print "Return code: ", p.returncode
print out.rstrip(), err.rstrip()

4
如果硬编码指令可以提高可读性,我认为这是可以接受的。 - Adam Matan

57

如何从Python中执行程序或调用系统命令

简单来说,使用subprocess.run即可,它会返回一个CompletedProcess对象:

>>> from subprocess import run
>>> from shlex import split
>>> completed_process = run(split('python --version'))
Python 3.8.8
>>> completed_process
CompletedProcess(args=['python', '--version'], returncode=0)

run函数想要一个词法解析后的shell参数列表 - 这就是你在shell中键入的内容,用空格分隔,但不包括被引号包含的空格,因此使用专门的函数split将你在shell中真正键入的内容分割开。

为什么这样做?

从Python 3.5开始,文档建议使用subprocess.run

调用子进程的推荐方法是对于所有可处理的用例都使用 run() 函数。对于更高级别的用例,可以直接使用底层的 Popen 接口。

以下是最简单用法的示例 - 它正好按照要求执行:

>>> from subprocess import run
>>> from shlex import split
>>> completed_process = run(split('python --version'))
Python 3.8.8
>>> completed_process
CompletedProcess(args=['python', '--version'], returncode=0)

run 等待命令成功完成,然后返回一个 CompletedProcess 对象。如果您指定了 timeout= 参数,它可能会引发 TimeoutExpired,如果失败并且您传递了 check=True,则可能会引发 CalledProcessError

正如您从上面的示例中推断出的那样,默认情况下,stdout 和 stderr 都会被导向到自己的 stdout 和 stderr。

我们可以检查返回的对象并查看给定的命令和返回码:

>>> completed_process.args
['python', '--version']
>>> completed_process.returncode
0

捕获输出

如果你想捕获输出,可以将subprocess.PIPE传递给相应的stderrstdout:

>>> from subprocess import PIPE
>>> completed_process = run(shlex.split('python --version'), stdout=PIPE, stderr=PIPE)
>>> completed_process.stdout
b'Python 3.8.8\n'
>>> completed_process.stderr
b''

这些属性返回字节。

传递命令列表

你可以通过手动提供命令字符串(就像问题中建议的那样)来轻松地从提供程序上构建字符串。 不要通过编程方式构建字符串。 这会存在潜在的安全问题。最好假设您不能信任输入。

>>> import textwrap
>>> args = ['python', textwrap.__file__]
>>> cp = run(args, stdout=subprocess.PIPE)
>>> cp.stdout
b'Hello there.\n  This is indented.\n'

请注意,只应按位置传递args

完整签名

这是源代码中的实际签名,并且如help(run)所示:

def run(*popenargs, input=None, timeout=None, check=False, **kwargs):
popenargskwargs会传递给Popen构造函数。input可以是一个字节字符串(或Unicode,如果指定了编码或universal_newlines=True),该字符串将被管道发送到子进程的标准输入。timeout=check=True的含义可在文档中找到:
timeout参数传递给Popen.communicate()。如果超时,则子进程将被杀死并等待。在子进程终止后,将重新引发TimeoutExpired异常。
如果checkTrue,并且进程以非零退出代码退出,则会引发CalledProcessError异常。该异常的属性保存了参数、退出代码以及如果捕获了它们则为stdout和stderr。
这个例子用check=True更好理解:
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

扩展签名

以下是文档中给出的扩展签名:

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, 
shell=False, cwd=None, timeout=None, check=False, encoding=None, 
errors=None)

请注意,这表示仅应按位置传递args列表。因此,请将其余参数作为关键字参数传递。

Popen

何时使用Popen?我很难基于参数找到用例。然而,直接使用Popen将使您访问其方法,包括poll,'send_signal','terminate'和'wait'。

以下是源代码中给出的Popen签名。 我认为这是最精确地封装信息的方式(而不是使用help(Popen)):


def __init__(self, args, bufsize=-1, executable=None,
             stdin=None, stdout=None, stderr=None,
             preexec_fn=None, close_fds=True,
             shell=False, cwd=None, env=None, universal_newlines=None,
             startupinfo=None, creationflags=0,
             restore_signals=True, start_new_session=False,
             pass_fds=(), *, user=None, group=None, extra_groups=None,
             encoding=None, errors=None, text=None, umask=-1, pipesize=-1):

但更有用的是Popen文档

subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, 
stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None,
env=None, universal_newlines=None, startupinfo=None, creationflags=0, 
restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, 
extra_groups=None, user=None, umask=-1, encoding=None, errors=None, 
text=None)

Execute a child program in a new process. On POSIX, the class uses os.execvp()-like behavior to execute the child program. On Windows, the class uses the Windows CreateProcess() function. The arguments to Popen are as follows.

理解有关Popen的其余文档将留给读者作为练习。


1
一个简单的主进程和子进程之间双向通信的示例可以在这里找到:https://dev59.com/CqTia4cB1Zd3GeqP7gk7#52841475 - James Hirschorn

55

使用subprocess

...或者对于一个非常简单的命令:

import os
os.system('cat testfile')

47
截至2018年6月27日发布的Python 3.7.0(https://docs.python.org/3/whatsnew/3.7.html),你可以以最强大且简单的方式实现你所需的结果。本答案旨在简要介绍各种选项的基本摘要。有关详细答案,请参见其他答案。

2021年的TL;DR

os.system(...)的最大优点是简单易用。而subprocess更好,尤其在Python 3.5及以后版本中使用仍然很容易。

import subprocess
subprocess.run("ls -a", shell=True)

注意: 这是你问题的确切答案 - 运行一个像在shell中一样的命令

像在shell中一样


首选方法

如果可能的话,去掉Shell开销并直接运行命令(需要一个列表)。

import subprocess
subprocess.run(["help"])
subprocess.run(["ls", "-a"])

以列表形式传递程序参数。 不要为包含空格的参数包括\"转义。


高级用例

检查输出

以下代码不言自明:

import subprocess
result = subprocess.run(["ls", "-a"], capture_output=True, text=True)
if "stackoverflow-logo.png" in result.stdout:
    print("You're a fan!")
else:
    print("You're not a fan?")

result.stdout是正常程序输出,不包括错误信息。阅读result.stderr以获得它们。

capture_output=True - 打开捕获功能。否则result.stderrresult.stdout将为None。自Python 3.7起可用。

text=True - 是一个方便的参数,添加于Python 3.7,可将接收到的二进制数据转换为易于处理的Python字符串。

检查返回码

执行:

if result.returncode == 127: print("The program failed for some weird reason")
elif result.returncode == 0: print("The program succeeded")
else: print("The program failed unexpectedly")

如果你只想检查程序是否成功(returncode == 0),否则抛出异常,那么有一个更方便的函数:{{函数名}}。
result.check_returncode()

但这是Python,所以有一个更方便的参数check,它可以自动为您执行相同的操作:

result = subprocess.run(..., check=True)

标准错误应该在标准输出内

您可能希望将所有程序输出都放在标准输出中,包括错误。要实现这一点,请运行

result = subprocess.run(..., stderr=subprocess.STDOUT)

result.stderr将变为None,而result.stdout将包含所有内容。

使用带参数字符串的shell=False

shell=False需要一个列表参数。但是,您可以使用shlex自行拆分参数字符串。

import subprocess
import shlex
subprocess.run(shlex.split("ls -a"))

就是这样。

常见问题

你可能刚开始使用Python时遇到了这个问题。让我们看一些常见问题。

FileNotFoundError: [Errno 2] 没有那个文件或目录: 'ls -a': 'ls -a'

你正在运行一个没有 shell=True 的子进程。要么使用列表 (["ls", "-a"]),要么设置 shell=True

TypeError: [...] NoneType [...]

检查是否已设置 capture_output=True

TypeError: 需要一个类似字节的对象,而不是 [...]

您始终从程序接收字节结果。如果想像普通字符串一样处理它,请设置 text=True

subprocess.CalledProcessError: 命令 '[...]' 返回非零退出状态 1。

您的命令未成功运行。您可以禁用 returncode 检查或检查实际程序的有效性。

TypeError: init() got an unexpected keyword argument [...]

您可能正在使用早于 3.7.0 版本的 Python;请更新到最新版本。否则,此 Stack Overflow 帖子中还有其他答案显示您的旧版替代解决方案。


1
os.system(...) 的优势在于简单易用。subprocess 更好。subprocess 有什么优点?我一直很愉快地使用 os.system,不确定转换到 subprocess 并记住额外的 shell=True 对我有什么帮助。在 subprocess 中有哪些更好的东西呢? - reducing activity
1
你说得对,os.system(...) 在简单的“盲目”执行方面是一个合理的选择。然而,使用情况相当有限 - 一旦你想要捕获输出,你就必须使用另一个库,然后你开始在代码中使用两个类似的用例 - subprocess 和 os。我更喜欢保持代码整洁,只使用其中一个。其次,我本来会把这一部分放在最前面,但 TL;DR 必须准确回答问题,你不应该使用 shell=True,而是使用我在“首选方式”部分中写的方法。 - fameman
1
os.system(...)shell=True 的问题在于,它会生成一个新的 shell 进程来执行你的命令。这意味着你必须手动进行转义,这并不像你想象的那么简单,特别是当你同时针对 POSIX 和 Windows 时。对于用户提供的输入,这是不可行的(想象一下用户输入了带有 " 引号的内容 - 你还需要对它们进行转义)。此外,shell 进程本身可能会加载你不需要的代码 - 它不仅会延迟程序,而且还可能导致意外的副作用,最终导致错误的返回代码。 - fameman
2
总之,os.system(...)是可以使用的。但是,一旦你写的不仅仅是一个快速的Python辅助脚本,我建议你使用没有shell=True的subprocess.run。关于os.system的缺点的更多信息,我想建议您阅读这个SO答案:https://dev59.com/P1cP5IYBdhLWcg3wNnjW#44731082 - fameman

43

os.system 可以使用,但有点过时。 它也不是非常安全。 相反,请尝试使用 subprocesssubprocess 不直接调用 sh,因此比 os.system 更安全。

这里获取更多信息。


3
虽然我同意总体建议,但是 subprocess 并不能消除所有安全问题,而且它本身也有一些讨厌的问题。 - tripleee

40

还有Plumbum

>>> from plumbum import local
>>> ls = local["ls"]
>>> ls
LocalCommand(<LocalPath /bin/ls>)
>>> ls()
u'build.py\ndist\ndocs\nLICENSE\nplumbum\nREADME.rst\nsetup.py\ntests\ntodo.txt\n'
>>> notepad = local["c:\\windows\\notepad.exe"]
>>> notepad()                                   # Notepad window pops up
u''                                             # Notepad window is closed by user, command returns

需要解释一下。 - Peter Mortensen

33

使用:

import os

cmd = 'ls -al'

os.system(cmd)

os - 这个模块提供了使用操作系统相关功能的便捷方式。

有关更多 os 函数,请参阅此处的文档。


2
它也已被弃用。请使用subprocess。 - Corey Goldberg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接