题 在Python中调用外部命令


如何在Python脚本中调用外部命令(就像我在Unix shell或Windows命令提示符下键入它一样)?


3635
2017-09-18 01:35


起源




答案:


看着那(这 子进程模块 在标准库中:

from subprocess import call
call(["ls", "-l"])

的优点  与 系统 是它更灵活(你可以获得stdout,stderr,“真正的”状态代码,更好的错误处理等...)。

官方文件 推荐  替代os.system()上的模块:

 模块提供了更强大的工具来产生新流程并检索其结果;使用该模块比使用此功能更可取[os.system()]。

用子进程模块替换旧函数“节中的部分  文档可能有一些有用的食谱。

官方文件  模块:


3504
2017-09-18 01:39



有没有办法使用变量替换? IE我试着做 echo $PATH 通过使用 call(["echo", "$PATH"]),但它只是回应了文字字符串 $PATH 而不是做任何替代。我知道我可以获得PATH环境变量,但我想知道是否有一种简单的方法让命令的行为完全像我在bash中执行它一样。 - Kevin Wheeler
@KevinWheeler你必须使用 shell=True 为了工作。 - SethMMorton
@KevinWheeler你不应该使用 shell=True为此,Python自带了 os.path.expandvars。在你的情况下你可以写: os.path.expandvars("$PATH")。 @SethMMorton请重新考虑您的评论 - > 为什么不使用shell = True - Murmel
从Python 3.5开始,建议您使用 subprocess.run 代替 subprocess.call。 docs.python.org/3/library/subprocess.html - Hannes Karppila
示例调用 ls -l 但不允许访问其输出(stdout 不可访问)。我发现这令人困惑 - 你可以使用没有stdout的命令,例如 touch。 - florisla


以下是调用外部程序的方法及其优缺点的摘要:

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

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

    但是,虽然这很方便,但您必须手动处理shell字符的转义,例如空格等。另一方面,这也允许您运行只是shell命令而不是外部程序的命令。看到 文件

  2. stream = os.popen("some_command with args") 会做同样的事情 os.system 除了它为您提供了一个类似文件的对象,您可以使用它来访问该进程的标准输入/输出。还有3种其他的popen变体,它们对i / o的处理方式略有不同。如果您将所有内容都作为字符串传递,那么您的命令将传递给shell;如果你将它们作为列表传递,那么你不必担心逃避任何事情。看到 文件

  3. Popen 的一类 subprocess 模块。这是为了替代 os.popen 但由于如此全面,因此具有稍微复杂一点的缺点。例如,你会说:

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

    代替:

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

    但是在一个统一的类而不是4个不同的popen函数中拥有所有选项是很好的。看到 文件

  4. call 功能来自 subprocess 模块。这基本上就像是 Popen class并获取所有相同的参数,但它只是等待命令完成并为您提供返回代码。例如:

    return_code = subprocess.call("echo Hello World", shell=True)  
    

    看到 文件

  5. 如果您使用的是Python 3.5或更高版本,则可以使用新的 subprocess.run 功能,这很像上面但更灵活,返回一个 CompletedProcess 命令完成执行时的对象。

  6. os模块还具有C程序中的所有fork / exec / spawn函数,但我不建议直接使用它们。

subprocess 模块应该是你使用的。

最后请注意,对于所有将shell执行的最终命令作为字符串传递的方法,您负责转义它。 存在严重的安全隐患 如果您传递的字符串的任何部分无法完全信任。例如,如果用户正在输入字符串的某些/任何部分。如果您不确定,请仅将这些方法与常量一起使用。为了给您一些暗示,请考虑以下代码:

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

并想象用户输入“我的妈妈不爱我&& rm -rf /”。


2469
2017-09-18 13:11



很好的答案/解释。这个答案如何证明Python的座右铭正如本文所述? fastcompany.com/3026446/...  “在风格上,Perl和Python有不同的哲学.Perl最着名的格言是”有多种方法可以做到这一点。“Python的设计有一个明显的方法来做到这一点”看起来它应该是另一种方式!在Perl中,我只知道两种执行命令的方法 - 使用back-tick或 open。 - Jean
如果使用Python 3.5+,请使用 subprocess.run()。 docs.python.org/3.5/library/subprocess.html#subprocess.run - phoenix
通常需要知道的是子进程的STDOUT和STDERR做了什么,因为如果忽略它们,在某些(非常常见的)条件下,子进程最终会发出系统调用来写入STDOUT(STDERR也是?)这将超过操作系统为进程提供的输出缓冲区,操作系统将阻止它直到某个进程从该缓冲区读取。所以,用目前推荐的方式, subprocess.run(..)究竟是什么 “默认情况下,这不会捕获stdout或stderr。” 意味着?关于什么 subprocess.check_output(..) 和STDERR? - Evgeni Sergeev
你推荐哪些命令阻止我的脚本?即如果我想在a中运行多个命令 for 循环如何在没有阻止我的python脚本的情况下执行此操作?我不关心命令的输出我只想运行它们。 - Charlie Parker
@phoenix我不同意。没有什么能阻止你在python3中使用os.system docs.python.org/3/library/os.html#os.system - Qback


我通常使用:

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


257
2017-09-18 18:20



.readlines() 读 所有 一次行,即它阻塞直到子进程退出(关闭它的管道末端)。要实时阅读(如果没有缓冲问题),您可以: for line in iter(p.stdout.readline, ''): print line, - jfs
你能详细说明“如果没有缓冲问题”是什么意思吗?如果进程明确阻塞,则子进程调用也会阻塞。我的原始例子也可能发生同样的情况。缓冲还会发生什么? - EmmEff
子进程可以在非交互模式下使用块缓冲而不是行缓冲 p.stdout.readline() (注意:没有 s 在孩子填充缓冲区之前,不会看到任何数据。如果孩子没有产生太多数据,那么输出将不是实时的。看到第二个原因 问:为什么不使用管道(popen())?。提供了一些解决方法 在这个答案 (pexpect,pty,stdbuf) - jfs
缓冲问题仅在您希望实时输出时才有意义,并且不适用于直到之后不打印任何内容的代码 所有 收到数据 - jfs


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

假设您想从CGI脚本启动一个长任务,即子进程应该比CGI脚本执行过程更长寿。

子流程模块docs的经典示例是:

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(win xp)上,父进程将不会完成,直到longtask.py完成其工作。这不是你想要的CGI脚本。问题不是Python特有的,在PHP社区中问题是一样的。

解决方案是通过DETACHED_PROCESS 流程创建标志 到win 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中启动后台进程尚未解决任何问题。


158
2018-02-12 10:15



我注意到在pydev + eclipse中开发py2exe应用程序可能存在“怪癖”。我能够告诉主脚本没有分离,因为eclipse的输出窗口没有终止;即使脚本执行完成,它仍然在等待返回。但是,当我尝试编译py2exe可执行文件时,会发生预期的行为(将进程作为分离运行,然后退出)。我不确定,但可执行文件名称不再在进程列表中。这适用于所有方法(os.system(“start *”),os.spawnl与os.P_DETACH,子程序等) - maranas
Windows问题:即使我使用DETACHED_PROCESS生成进程,当我杀死我的Python守护进程时,由它打开的所有端口都不会释放,直到所有生成的进程终止。 WScript.Shell解决了我所有的问题。这里的例子: pastebin.com/xGmuvwSx - Alexey Lebedev
您可能还需要CREATE_NEW_PROCESS_GROUP标志。看到 即使直系孩子已经终止,Popen仍在等待孩子的过程 - jfs
以下是不正确的:“[o] n windows(win xp),父进程将不会完成,直到longtask.py完成其工作”。父进程将正常退出,但控制台窗口(conhost.exe实例)仅在最后一个附加进程退出时关闭,并且子进程可能继承了父进程控制台。设置 DETACHED_PROCESS 在 creationflags 通过阻止子进程继承或创建控制台来避免这种情况。如果您想要一个新的控制台,请使用 CREATE_NEW_CONSOLE (0x00000010)。 - eryksun
我并不是说作为分离进程执行是不正确的。也就是说,您可能需要将标准句柄设置为文件,管道或 os.devnull 因为某些控制台程序会以错误退出。当您希望子进程与父进程同时与用户交互时,创建一个新控制台。尝试在一个窗口中同时执行这两个操作会很困惑。 - eryksun


我建议使用子进程模块而不是os.system,因为它会为你进行shell转义,因此更加安全: http://docs.python.org/library/subprocess.html

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

98
2017-09-18 01:42





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

如果要返回命令的结果,可以使用 os.popen。但是,从版本2.6开始,这已经被弃用了 子进程模块,其他答案已经涵盖得很好。


94
2017-09-18 01:37



POPEN 已弃用 有利于 子。 - Fox Wilson
您也可以使用os.system调用保存结果,因为它的工作方式类似于UNIX shell本身,例如os.system('ls -l> test2.txt') - Stefan Gruenwald


import os
os.system("your command")

请注意,这是危险的,因为未清除该命令。我把它留给你去谷歌搜索关于'os'和'sys'模块的相关文档。有许多函数(exec *和spawn *)可以执行类似的操作。


84
2017-09-18 01:37



你是什​​么意思 “命令没有清理”? - Peter Mortensen
不知道我近十年前的意思(检查日期!),但如果我不得不猜测,那就是没有完成验证。 - nimish


我总是用 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)

52
2018-03-13 00:12





检查“pexpect”Python库。

它允许交互式控制外部程序/命令,甚至ssh,ftp,telnet等。您只需键入以下内容:

child = pexpect.spawn('ftp 192.168.0.24')

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

child.sendline('anonymous')

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

52
2017-10-07 07:09





有许多不同的库允许您使用Python调用外部命令。对于每个库,我已经给出了描述并显示了调用外部命令的示例。我用作例子的命令是 ls -l (列出所有文件)。如果您想了解有关我列出的任何库的更多信息,并链接每个库的文档。

资料来源:

这些都是库:

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

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

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.system 和 os.popen (注意:还有一个subprocess.popen)。操作系统将始终运行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是一个用于“类似脚本”的Python程序的库。您可以调用类似函数的程序 sh。如果要运行没有shell的管道,Plumbum很有用。

ls_cmd = plumbum.local("ls -l") # get command
ls_cmd() # run command

Pexpect的

pexpect允许您生成子应用程序,控制它们并在其输出中查找模式。对于期望在Unix上使用tty的命令,这是替代子进程的更好的替代方法。

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

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

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

使者

特使被称为“人类的子过程”。它被用作围绕它的便利包装 subprocess 模块。

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

命令

commands 包含for的包装函数 os.popen,但它已从Python 3中删除 subprocess 是一个更好的选择。

该编辑基于J.F. Sebastian的评论。


48
2017-10-29 14:02