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

6066

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

66个回答

5754
使用subprocess.run
import subprocess

subprocess.run(["ls", "-l"]) 

另一种常见的方法是使用os.system,但你不应该使用它,因为如果命令的任何部分来自程序外部或可能包含空格或其他特殊字符,则会存在安全隐患。此外,subprocess.run通常更加灵活(你可以获取stdoutstderr"真实"状态码、更好的错误处理等)。即使os.system的文档也建议使用subprocess代替。
在Python 3.4及更早版本中,使用subprocess.call而不是.run
subprocess.call(["ls", "-l"])

9
有没有一种方法可以使用变量替换?例如,我尝试使用call(["echo", "$PATH"])来执行echo $PATH,但它只是回显了字面字符串$PATH而没有做任何替换。我知道可以获取PATH环境变量,但我想知道是否有一种简单的方法,使命令的行为与在bash中执行它的行为完全相同。 - Kevin Wheeler
@KevinWheeler 你需要使用 shell=True 才能让它工作。 - SethMMorton
73
@KevinWheeler 你不应该使用 shell=True,Python中有 os.path.expandvars 函数可以实现同样的功能。在你的情况下,你可以这样写:os.path.expandvars("$PATH")。@SethMMorton 请重新考虑你的评论 -> 为什么不要使用 shell=True - Murmel
如果我想要使用管道,比如 pip list | grep anatome,该怎么办? - Charlie Parker
无法工作。我尝试了,它对大多数命令有效,但并非所有命令都有效。 - thatrandomperson
显示剩余2条评论

3535

以下是调用外部程序的方法总结,包括它们的优缺点:

  1. os.system将命令和参数传递给系统的shell。这很好,因为您可以以这种方式运行多个命令,并设置管道和输入/输出重定向。例如:

  2. os.system("some_command < input_file | another_command > output_file")  
    

    然而,虽然这很方便,但你需要手动处理shell字符的转义,例如空格等。另一方面,这也让你运行仅仅是shell命令而不是实际外部程序的命令。

    os.popenos.system一样,但它给了你一个类似文件的对象,可以用来访问该进程的标准输入/输出。有三种其他变体的popen,它们都略微处理i/o。如果将所有内容作为字符串传递,则命令会传递到shell;如果将它们作为列表传递,则无需担心任何转义。例如:

    print(os.popen("ls -l").read())
    
  3. subprocess.Popen。这是 os.popen 的替代品,但它稍微复杂一些,因为功能比较全面。例如,您可以这样说:

  4. print subprocess.Popen("echo Hello World", shell=True, stdout=subprocess.PIPE).stdout.read()
    

    代替

    print os.popen("echo Hello World").read()
    

    不过,将所有选项统一放在一个类中而不是4个不同的popen函数中确实很不错。详见文档

    subprocess.call。这基本上与Popen类相同,接受所有相同的参数,但它只是等待命令完成并给您返回代码。例如:

    return_code = subprocess.call("echo Hello World", shell=True)
    
  5. subprocess.run是Python 3.5+中的模块,与上面介绍的模块类似,但更加灵活并在命令完成执行后返回一个CompletedProcess对象。

  6. os.forkos.execos.spawn与其C语言对应项类似,但不建议直接使用它们。

您应该使用subprocess模块。

最后,请注意,对于所有通过字符串传递给shell以执行最终命令的方法,您需要负责对其进行转义。如果传递的字符串的任何部分不能完全信任,则会导致严重的安全问题。例如,如果用户输入了字符串的某个或全部部分。如果您不确定,请仅使用常量这些方法。以下代码将让您意识到这样做的后果:


print subprocess.Popen("echo %s " % user_input, stdout=PIPE).stdout.read()

并且想象一下用户输入了类似于“my mama didnt love me && rm -rf /”的内容,这样做可能会删除整个文件系统。


55
这个回答如何支持这篇文章中描述的Python口号?http://www.fastcompany.com/3026446/the-fall-of-perl-the-webs-most-promising-language“在风格上,Perl和Python有不同的哲学。Perl最著名的座右铭之一是 “有多种方法可以做到”。Python旨在拥有一种明显的方法来做到这一点。”这似乎应该相反!在Perl中,我只知道两种执行命令的方式-使用反引号或open。这个回答并没有直接回答文章中Python口号的合理性,因此需要进一步解释。Python的口号是"优美胜于丑陋,明了胜于晦涩,简洁胜于复杂",它强调代码的可读性和易用性。与Perl不同,Python鼓励开发者遵循一种主要的编程方式,在减少错误和提高代码可读性方面具有优势。虽然Perl也有其独特的优点,但它的灵活性和允许多种编程方式的特点可能会导致代码难以维护。 - Jean
28
如果使用Python 3.5或更高版本,请使用subprocess.run()。https://docs.python.org/3.5/library/subprocess.html#subprocess.run - phoenix
11
需要了解的是子进程的标准输出(STDOUT)和标准错误输出(STDERR)的处理情况,因为如果这些输出被忽略,在某些(相当普遍的)情况下,最终子进程将发出一个系统调用以写入STDOUT(STDERR也可能),超过操作系统为该进程提供的输出缓冲区,并且操作系统将导致它阻塞,直到有进程从该缓冲区中读取。因此,根据当前推荐的方法subprocess.run(...),默认情况下,“这不会捕获stdout或stderr。”的含义是什么?对于subprocess.check_output(...)和STDERR呢? - Evgeni Sergeev
14
这种做法可能是错误的。大多数人只需要使用subprocess.run()或者它的老版本subprocess.check_call()等等方法。对于这些方法不能满足需求的情况,可以考虑使用subprocess.Popen()。也许应该避免提及os.popen(),或者将其放在“自己编写fork/exec/spawn代码”之后。 - tripleee
请问何时使用"shell=True",何时不使用? - Ben L
显示剩余2条评论

434

典型实现:

import subprocess

p = subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in p.stdout.readlines():
    print line,
retval = p.wait()

您可以自由地处理管道中的stdout数据。事实上,您甚至可以省略这些参数(stdout=stderr=),它将像os.system()一样运行。


59
.readlines() 一次性读取所有行,也就是说它会一直阻塞直到子进程退出(关闭其管道的一端)。如果要实时读取(如果没有缓冲问题),可以使用以下代码:for line in iter(p.stdout.readline, ''): print line, - jfs
4
请问您的“if there is no buffering issues”的意思是什么?如果进程被阻塞,子进程调用也会被阻塞。我的原始示例也可能出现这种情况。关于缓冲方面还可能发生什么其他情况? 如果没有缓冲问题,指的是在数据传输过程中没有延迟或数据包丢失等问题。如果进程被阻塞,子进程调用也会被阻塞。与我的原始示例相同,也可能发生这种情况。关于缓冲方面可能发生的其他事情是什么? - EmmEff
20
在非交互模式下,子进程可能会使用块缓冲而不是行缓冲,因此 p.stdout.readline()(注意:末尾没有s)在子进程填满其缓冲区之前不会看到任何数据。如果子进程没有产生太多数据,则输出将不会实时显示。请参见 Q: Why not just use a pipe (popen())? 中的第二个原因。一些解决方法可以在 此答案 中找到(例如 pexpect、pty、stdbuf)。 - jfs
7
只有在需要实时输出结果时,缓冲问题才会产生影响,并且不适用于你的代码,因为它直到接收到 所有 数据才会打印任何内容。 - jfs
10
这个回答在当时是可以的,但现在我们不应该再推荐使用Popen来执行简单任务了。这也没有必要指定shell=True参数。可以尝试使用subprocess.run()方法中的一个答案。 - tripleee

277

关于如何将子进程与调用者进程分离的一些提示(在后台启动子进程)。

假设您想从CGI脚本中开始一个长时间运行的任务。即,子进程应该比CGI脚本执行进程存活更长时间。

subprocess模块文档中的经典示例为:

import subprocess
import sys

# Some code here

pid = subprocess.Popen([sys.executable, "longtask.py"]) # Call subprocess

# Some more code here

这里的想法是,您不希望在“调用子进程”的行中等待longtask.py完成。但是从示例中,“一些其他代码”后面会发生什么并不清楚。

我的目标平台是FreeBSD,但开发是在Windows上进行的,所以我首先在Windows上遇到了问题。

在Windows(Windows XP)上,父进程将不会完成,直到longtask.py完成其工作。这不是CGI脚本中所期望的。该问题不特定于Python;在PHP社区中,问题也是相同的。

解决方案是将DETACHED_PROCESS进程创建标志传递给Windows API的底层CreateProcess函数。如果您已安装pywin32,则可以从win32process模块导入该标志,否则您应该自己定义它:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid

/*UPD 2015.10.27 @eryksun在下面的评论中指出,语义上正确的标志是CREATE_NEW_CONSOLE(0x00000010)*/

在FreeBSD上,我们有另一个问题:当父进程结束时,它也会结束子进程。这在CGI脚本中也不是你想要的。一些实验表明,问题似乎在于共享sys.stdout。工作解决方案如下:

pid = subprocess.Popen([sys.executable, "longtask.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)

我没有在其他平台上检查过代码,也不知道其在FreeBSD上表现异常的原因。如果有人知道,请分享您的想法。目前在Python中启动后台进程的Google搜索并没有帮助解决问题。


3
你可能还需要使用CREATE_NEW_PROCESS_GROUP标志。请参见Popen等待子进程,即使直接的子进程已终止 - jfs
2
我发现 import subprocess as sp;sp.Popen('calc') 没有等待子进程完成。似乎 creationflags 不是必要的。我错过了什么? - ubershmekel
1
@ubershmekel,我不确定你的意思,也没有Windows安装程序。如果我记得正确,如果没有标志,您无法关闭启动“calc”的“cmd”实例。 - newtover
10
以下内容不正确:“[在Windows(Win XP)上],父进程将等待longtask.py完成工作后才会结束。” 父进程将正常退出,但控制台窗口(conhost.exe实例)仅在最后一个已附加的进程退出时关闭,而子进程可能已经继承了父进程的控制台。通过在“creationflags”中设置“DETACHED_PROCESS”,可以避免子进程继承或创建控制台。如果您需要一个新的控制台,请使用CREATE_NEW_CONSOLE(0x00000010)。 - Eryk Sun
3
我并不是说将进程作为分离进程执行是错误的。但是,由于某些控制台程序否则会退出并显示错误,因此您可能需要将标准句柄设置为文件、管道或 os.devnull。当您希望子进程与父进程同时与用户交互时,请创建一个新的控制台。在单个窗口中同时进行这两个操作可能会令人困惑。 - Eryk Sun
显示剩余6条评论

223
import os
os.system("your command")

请注意,这是一种危险的做法,因为该命令未被清理。请参阅{{link1:os}}和 {{link2:sys}} 模块的文档。存在许多类似功能的函数({{link3:exec*}}和{{link4:spawn*}})。

19
不知道我十年前想表达什么(请检查日期!),但如果我必须猜的话,那就是没有进行验证。 - nimish
4
现在应该将其指向subprocess,这是一个稍微更加通用和可移植的解决方案。运行外部命令本质上是不可移植的(您必须确保命令在您需要支持的每个架构上都可用),并且将用户输入作为外部命令传递本质上是不安全的。 - tripleee
你如何获取输出? - Nathan B
@NathanB 可以使用 result = subprocess.run(),然后执行 result.stdout 来代替。也可以通过拦截 stdout来实现。 - user3064538

176

我建议使用 subprocess 模块来代替 os.system,因为它可以为您进行 shell 转义,因此更加安全。

subprocess.call(['ping', 'localhost'])

如果你想从带参数的命令中创建一个列表,这个列表可以在shell=False时与subprocess一起使用,那么可以使用shlex.split来轻松完成此操作。https://docs.python.org/2/library/shlex.html#shlex.split(根据文档,这是推荐的方法 https://docs.python.org/2/library/subprocess.html#popen-constructor) - Daniel F
14
这句话是错误的:“它会为你进行Shell转义,因此更安全”。子进程模块不会进行Shell转义,也不会通过Shell传递你的命令,因此没有必要进行Shell转义。 - Lie Ryan

171
import os
cmd = 'ls -al'
os.system(cmd)

如果您想返回命令的结果,可以使用os.popen。但是,自2.6版本起,它已被subprocess模块取代,其他答案已经很好地涵盖了此模块。


16
popen已被弃用,建议使用subprocess代替。 - tew
你也可以使用os.system调用保存你的结果,因为它像UNIX shell本身一样工作,例如os.system('ls -l > test2.txt')。 - Stefan Gruenwald

135

有很多不同的库可以让你在Python中调用外部命令。对于每个库,我都给出了描述,并展示了调用外部命令的示例。我使用的命令示例是ls -l(列出所有文件)。如果您想了解更多关于我列出的任何库的信息,我已经为每个库链接了文档。

Sources

这些都是库

希望这能帮助您决定使用哪个库 :)

subprocess

Subprocess允许您调用外部命令并将其连接到它们的输入/输出/错误管道(stdin、stdout和stderr)。 Subprocess是运行命令的默认选择,但有时其他模块更好。

subprocess.run(["ls", "-l"]) # Run command
subprocess.run(["ls", "-l"], stdout=subprocess.PIPE) # This will run the command and return any output
subprocess.run(shlex.split("ls -l")) # You can also use the shlex library to split the command

os

os用于"操作系统相关的功能"。它还可以通过os.systemos.popen调用外部命令(注意:还有一个subprocess.popen)。os始终会运行shell,对于那些不需要或不知道如何使用subprocess.run的人来说,它是一个简单的替代方案。

os.system("ls -l") # Run command
os.popen("ls -l").read() # This will run the command and return any output

sh

sh是一个子进程接口,可以让你像调用函数一样调用程序。如果你需要多次运行一个命令,这非常有用。

sh.ls("-l") # Run command normally
ls_cmd = sh.Command("ls") # Save command as a variable
ls_cmd() # Run command as if it were a function

plumbum

plumbum是一个用于类似脚本的Python程序的库。你可以像在sh中那样调用程序作为函数来运行。如果你想要在没有shell的情况下运行一个管道,Plumbum就非常有用。

ls_cmd = plumbum.local("ls -l") # Get command
ls_cmd() # Run command

pexpect

pexpect可以启动子应用程序、控制它们并查找它们的输出模式。对于在Unix上需要tty的命令,这是subprocess的一个更好的替代方案。

pexpect.run("ls -l") # Run command as normal
child = pexpect.spawn('scp foo user@example.com:.') # Spawns child application
child.expect('Password:') # When this is the output
child.sendline('mypassword')

面料

面料是一个适用于Python 2.5和2.7的库。它允许您执行本地和远程shell命令。面料是在安全shell(SSH)中运行命令的简单替代方法。

fabric.operations.local('ls -l') # Run command as normal
fabric.operations.local('ls -l', capture = True) # Run command and receive output

envoy

envoy被称为“人类可用的子进程(subprocess)”。它是subprocess模块的便捷包装器。

r = envoy.run("ls -l") # Run command
r.std_out # Get output

命令

commands 包含了对 os.popen 的包装函数,但自从出现更好的替代品 subprocess 后,在 Python 3 中已被移除。


我认为应该是 ls_cmd = plumbum.local["ls -l"] # 获取命令 - innisfree

88

使用标准库

使用 subprocess 模块(Python 3):

import subprocess
subprocess.run(['ls', '-l'])

这是推荐的标准方式。然而,更复杂的任务(管道、输出、输入等)可能会使构建和编写过程变得乏味。

关于Python版本的说明:如果您仍在使用Python 2,subprocess.call 的工作方式类似。

专业提示:如果您不想(或无法)以列表形式提供命令,则shlex.split 可以帮助您解析 runcall 和其他 subprocess 函数的命令:

import shlex
import subprocess
subprocess.run(shlex.split('ls -l'))

带有外部依赖项

如果您不介意使用外部依赖项,请使用 plumbum

from plumbum.cmd import ifconfig
print(ifconfig['wlan0']())

这是最好的 subprocess 封装器。它跨平台,即它可以在 Windows 和类 Unix 系统上工作。使用 pip install plumbum 安装。

另一个流行的库是 sh

from sh import ifconfig
print(ifconfig('wlan0'))

然而,sh停止了对Windows的支持,因此它不如以前那么出色。安装方式为pip install sh


86

我通常使用fabric来完成这些任务。以下是演示代码:

from fabric.operations import local
result = local('ls', capture=True)
print "Content:/n%s" % (result, )

但这似乎是一个好工具:sh(Python子进程接口)

看一个例子:

from sh import vgdisplay
print vgdisplay()
print vgdisplay('-v')
print vgdisplay(v=True)

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