在Python中调用外部命令
如何在Python脚本中调用外部命令(就像我在Unix Shell或者Windows命令提示符下键入的一样)?
解决方案
import os os.system("your command")
请注意,这很危险,因为未清除命令。我留给你去谷歌搜索有关"操作系统"和"系统"模块的相关文档。有很多函数(exec *和spawn *)将执行类似的操作。
import os cmd = 'ls -al' os.system(cmd)
如果要返回命令的结果,可以使用os.popen
。但是,自2.6版以来,不推荐使用此方法,而支持子流程模块,其他答案已经很好地涵盖了此子流程模块。
查看标准库中的子流程模块:
from subprocess import call call(["ls", "-l"])
与系统相比,子流程的优势在于它更灵活(我们可以获取stdout,stderr,"真实"状态代码,更好的错误处理等)。
官方文档建议使用替代os.system()上的子流程模块:
The subprocess module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function [os.system()].
子流程文档中的"用子流程模块替换较旧的功能"部分可能有一些有用的配方。
有关子流程模块的正式文档:
- Python 2-子进程
- Python 3-子进程
我建议使用subprocess模块而不是os.system,因为它确实为我们进行外壳转义,因此更加安全:http://docs.python.org/library/subprocess.html
subprocess.call(['ping', 'localhost'])
https://docs.python.org/2/library/subprocess.html
...或者一个非常简单的命令:
import os os.system('cat testfile')
os.system可以,但是有点过时了。这也不是很安全。相反,尝试subprocess
。 subprocess不直接调用sh,因此比os.system更安全。
在此处获取更多信息。
下面总结了调用外部程序的方法以及每种方法的优缺点:
- os.system("带有args的some_command")将命令和参数传递给系统的shell。很好,因为我们实际上可以以这种方式一次运行多个命令,并设置管道和输入/输出重定向。例如:
os.system("some_command < input_file | another_command > output_file")
但是,尽管这很方便,但是我们必须手动处理外壳字符(例如空格等)的转义。另一方面,这还允许我们运行命令,这些命令只是shell命令,而实际上不是外部程序。请参阅文档。
stream = os.popen("带有args的some_command")
与os.system
的作用相同,除了它为我们提供了类似文件的对象,我们可以使用该对象访问该过程的标准输入/输出。 Popen还有其他3种变体,它们对I / O的处理略有不同。如果我们将所有内容都作为字符串传递,那么命令将传递给shell;否则,命令将传递给shell。如果将它们作为列表传递,则无需担心转义任何内容。请参阅文档。- 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函数是一个很好的选择。请参阅文档。
- 子过程模块中的调用函数。基本上就像
Popen
类一样,并接受所有相同的参数,但是它只是等到命令完成并为我们提供返回代码。例如:
return_code = subprocess.call("echo Hello World", shell=True)
请参阅文档。
- 如果我们使用的是Python 3.5或者更高版本,则可以使用新的
subprocess.run
函数,该函数与上面的函数非常相似,但是更加灵活,当命令执行完毕后,它会返回一个CompletedProcess对象。 - 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()"。
这里还有另一个以前没有提到的区别。
subprocess.Popen执行<command>作为子过程。就我而言,我需要执行文件<a>,该文件需要与另一个程序<b>通信。
我尝试了子流程,执行成功。但是<b>无法与<a>通信。
当我都从终端运行时,一切都正常。
多一个:
(注意:kwrite的行为与其他应用程序不同。如果在Firefox上尝试以下操作,结果将有所不同。)
如果我们尝试使用os.system(" kwrite"),程序流程将冻结,直到用户关闭kwrite。为了克服这个问题,我尝试了os.system(konsole -e kwrite)
。这个时间程序继续进行,但是kwrite成为了控制台的子进程。
任何人都运行kwrite而不是子进程(即,在系统监视器中,它必须出现在树的最左侧)。
关于从调用者中分离子进程的一些提示(在后台启动子进程)。
假设我们想从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中启动后台进程尚未透露任何信息。
还要检查" pexpect" Python库。
它允许交互式控制外部程序/命令,甚至包括ssh,ftp,telnet等。我们可以键入以下内容:
child = pexpect.spawn('ftp 192.168.0.24') child.expect('(?i)name .*: ') child.sendline('anonymous') child.expect('(?i)password')