从 Python 调用外部命令

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

答案

查看标准库中的子流程模块:

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

system相比, subprocess的优势在于它更灵活(您可以获取stdoutstderr ,“真实” 状态代码,更好的错误处理等)。

官方文档建议使用替代os.system()subprocess模块:

subprocess模块提供了更强大的功能来生成新流程并检索其结果。使用该模块优于使用此功能 [ os.system() ]。

subprocess文档中的用子流程模块替换较早的功能部分可能有一些有用的方法。

对于 3.5 之前的 Python 版本,请使用call

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

下面总结了调用外部程序的方法以及每种方法的优缺点:

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

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

但是,尽管这很方便,但是您必须手动处理外壳字符(如空格等)的转义。另一方面,这还允许您运行仅是外壳命令而不是外部程序的命令。请参阅文档

  1. stream = os.popen("some_command with args")os.system的作用相同,只是它为您提供了一个类似于文件的对象,您可以使用该对象访问该进程的标准输入 / 输出。 Popen 还有其他 3 种变体,它们对 I / O 的处理略有不同。如果您将所有内容都作为字符串传递,那么您的命令将传递到外壳程序;如果将它们作为列表传递,则无需担心转义任何内容。请参阅文档

  2. subprocess模块的Popen类。它打算替代os.popen但缺点是由于过于全面,因此稍微复杂一些。例如,您会说:

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

    代替:

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

    但是将所有选项都放在一个统一的类中而不是 4 个不同的 popen 函数是一个很好的选择。请参阅文档

  3. 来自subprocess模块的call函数。基本上,这和Popen类一样,并采用所有相同的参数,但是它只是等待命令完成并提供返回代码。例如:

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

    请参阅文档

  4. 如果您使用的是 Python 3.5 或更高版本,则可以使用新的subprocess.run函数,该函数与上面的代码非常相似,但是更加灵活,并在命令完成执行后返回CompletedProcess对象。

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

subprocess模块可能应该是您所使用的模块。

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

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

并想象用户输入了 “我的妈妈不爱我 && rm -rf /”,这可能会擦除整个文件系统。

典型的实现:

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

关于从调用者中分离子进程的一些提示(在后台启动子进程)。

假设您要从 CGI 脚本开始一个长任务。也就是说,子进程的生存期应比 CGI 脚本执行进程的生存期长。

子流程模块文档中的经典示例是:

import subprocess
import sys

# Some code here

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

# Some more code here

这里的想法是,您不希望在 long 任务行中完成之前在 “调用子进程” 行中等待。但是尚不清楚示例中 “这里有更多代码” 行之后会发生什么。

我的目标平台是 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 中启动后台进程尚未阐明。

我建议您使用 subprocess 模块而不是 os.system,因为它确实为您进行外壳转义,因此更加安全: http ://docs.python.org/library/subprocess.html

subprocess.call(['ping', 'localhost'])
import os
cmd = 'ls -al'
os.system(cmd)

如果要返回命令的结果,可以使用os.popen 。但是,自 2.6 版以来,不推荐使用此方法,而支持subprocess 模块 ,其他答案已经很好地涵盖了这一点。

import os
os.system("your command")

请注意,这很危险,因为未清除命令。我留给你去谷歌搜索有关 “操作系统” 和 “系统” 模块的相关文档。有很多函数(exec * 和 spawn *)将执行类似的操作。

有很多不同的库,可让您使用 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.systemos.popen调用外部命令(注意:还有一个os.popen )。 os 将始终运行该外壳程序,对于不需要或不知道如何使用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函数那样调用程序。如果您要在没有外壳的情况下运行管道,则 Plumbum 很有用。

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

期待

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包含os.popen包装函数,但由于subprocess是更好的选择,因此已从 Python 3 中删除了该函数。

该编辑基于 JF Sebastian 的评论。

还要检查 “pexpect” Python 库。

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

child = pexpect.spawn('ftp 192.168.0.24')

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

child.sendline('anonymous')

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

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