在Python中调用外部命令



Answers

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

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

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

    但是,虽然这很方便,但您必须手动处理诸如空格等外壳字符的转义。另一方面,这也可以让您运行仅仅是shell命令的命令,而不是实际上的外部程序。 请参阅文档

  2. stream = os.popen("some_command with args")将执行与os.system相同的操作,只不过它提供了一个类似文件的对象,您可以使用该对象访问该进程的标准输入/输出。 还有另外三种popen变体,它们都以不同的方式处理I / O。 如果你把所有东西都作为一个字符串传递,那么你的命令被传递给shell; 如果你将它们作为列表传递给你,那么你不需要担心逃脱任何事情。 请参阅文档

  3. 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函数是很好的。 请参阅文档

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

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

    请参阅文档

  5. 如果您使用的是Python 3.5或更高版本,则可以使用新的subprocess.run函数,该函数与上述类似,但更加灵活,并在命令执行CompletedProcess时返回CompletedProcess对象。

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

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

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

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

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

Question

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




更新:

如果您的代码不需要保持与早期Python版本的兼容性,那么subprocess.runPython 3.5的推荐方法。 它更一致,并提供与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')

原始答案:

我建议尝试https://github.com/kennethreitz/envoy 。 它是子进程的一个包装,它反过来旨在替换旧的模块和函数。 特使是人类的子进程。

自述文件中的示例用法:

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



这是我如何运行我的命令。 这段代码有你需要的一切

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



在Python中调用外部命令

很简单,使用subprocess.run ,它返回一个CompletedProcess对象:

>>> import subprocess
>>> completed_process = subprocess.run('python --version')
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
>>> completed_process
CompletedProcess(args='python --version', returncode=0)

为什么?

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

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

这里有一个最简单的用法的例子 - 它的确如所问:

>>> import subprocess
>>> completed_process = subprocess.run('python --version')
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
>>> completed_process
CompletedProcess(args='python --version', returncode=0)

run等待命令成功完成,然后返回一个CompletedProcess对象。 它可能会提高TimeoutExpired (如果您给它一个timeout=参数)或CalledProcessError (如果它失败并且您传递check=True )。

正如你从上面的例子中推断的那样,默认情况下stdout和stderr都会被传送到你自己的stdout和stderr。

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

>>> completed_process.args
'python --version'
>>> completed_process.returncode
0

捕获输出

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

>>> cp = subprocess.run('python --version', 
                        stderr=subprocess.PIPE, 
                        stdout=subprocess.PIPE)
>>> cp.stderr
b'Python 3.6.1 :: Anaconda 4.4.0 (64-bit)\r\n'
>>> cp.stdout
b''

(我发现它有趣并且有点违反直觉,版本信息被置于stderr而不是stdout。)

传递一个命令列表

人们可以轻松地从手动提供命令字符串(如问题建议)移动到提供以编程方式构建的字符串。 不要以编程方式构建字符串。 这是一个潜在的安全问题。 最好假设你不相信输入。

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

请注意,只有args应该通过位置传递。

全额签名

以下是源代码中的实际签名,如help(run)

def run(*popenargs, input=None, timeout=None, check=False, **kwargs):

popenargskwargs被赋予了Popen构造函数。 input可以是一个字符串(或者unicode,如果指定encoding或universal_newlines=True ),它将被传递给子进程的stdin。

该文档比我更好地描述了timeout=check=True

超时参数传递给Popen.communicate()。 如果超时过期,子进程将被终止并等待。 TimeoutExpired异常将在子进程终止后重新生成。

如果检查为真,并且该进程以非零退出代码退出,则会引发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)

请注意,这表示只有参数列表应该通过位置传递。 因此将其余参数作为关键字参数传递。

POPEN

当使用Popen来代替? 我很难仅根据论据找到用例。 但是,直接使用Popen将允许您访问其方法,包括poll ,“send_signal”,“终止”和“等待”。

这是来源中给出的Popen签名。 我认为这是信息的最精确封装(而不是help(Popen) ):

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

但更多的信息是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=False,
                 startupinfo=None, creationflags=0, restore_signals=True,
                 start_new_session=False, pass_fds=(), *, encoding=None, errors=None)

在新过程中执行子程序。 在POSIX上,该类使用类似os.execvp()的行为来执行子程序。 在Windows上,该类使用Windows的CreateProcess()函数。 Popen的论据如下。

了解Popen的剩余文档将留给读者作为练习。




从openstack中子获取网络ID:

#!/usr/bin/python
import os
netid= "nova net-list | awk '/ External / { print $2 }'"
temp=os.popen(netid).read()  /* here temp also contains new line (\n) */
networkId=temp.rstrip()
print(networkId)

星网表的输出

+--------------------------------------+------------+------+
| ID                                   | Label      | CIDR |
+--------------------------------------+------------+------+
| 431c9014-5b5d-4b51-a357-66020ffbb123 | test1      | None |
| 27a74fcd-37c0-4789-9414-9531b7e3f126 | External   | None |
| 5a2712e9-70dc-4b0e-9281-17e02f4684c9 | management | None |
| 7aa697f5-0e60-4c15-b4cc-9cb659698512 | Internal   | None |
+--------------------------------------+------------+------+

打印输出(networkId)

27a74fcd-37c0-4789-9414-9531b7e3f126



In Windows you can just import the subprocess module and run external commands by calling subprocess.Popen() , subprocess.Popen().communicate() and subprocess.Popen().wait() as below:

# Python script to run a command line
import subprocess

def execute(cmd):
    """
        Purpose  : To execute a command and return exit status
        Argument : cmd - command to execute
        Return   : exit_code
    """
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (result, error) = process.communicate()

    rc = process.wait()

    if rc != 0:
        print "Error: failed to execute command:", cmd
        print error
    return result
# def

command = "tasklist | grep python"
print "This process detail: \n", execute(command)

输出:

This process detail:
python.exe                     604 RDP-Tcp#0                  4      5,660 K



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

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




也检查“pexpect”Python库。

它允许外部程序/命令的交互式控制,甚至ssh,ftp,telnet等。你可以输入如下内容:

child = pexpect.spawn('ftp 192.168.0.24')

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

child.sendline('anonymous')

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



如果你不想测试返回值, subprocess.check_call是很方便的。 它在任何错误上抛出异常。




还有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



如果你需要的是你正在调用的命令的输出,
那么你可以使用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管道,文件名通配符,环境变量扩展以及将〜扩展到用户的home目录。 但是,请注意,Python本身提供了很多类似shell的功能(特别是globfnmatchos.walk()os.path.expandvars()os.path.expanduser()shutil )的实现。




os.system不允许你存储结果,所以如果你想在一些列表中存储结果或者subprocess.call有效。




os.system ,但有点过时。 这也不是很安全。 相反,尝试subprocesssubprocess os.system不直接调用sh,因此比os.system更安全。

here获取更多信息。




您可以使用Popen,然后您可以检查过程的状态:

from subprocess import Popen

proc = Popen(['ls', '-l'])
if proc.poll() is None:
    proc.kill()

检查出subprocess.Popen




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

假设你想从一个CGI脚本开始一个长任务,那就是子进程应该比CGI脚本的执行过程长。

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

import subprocess
import sys

# some code here

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

# some more code here

这里的想法是,你不想在'call subprocess'行中等待,直到longtask.py完成。 但是不清楚在这个例子中'这里有更多的代码'之后会发生什么。

我的目标平台是freebsd,但是开发是在Windows上进行的,所以我首先面对Windows上的问题。

在Windows(win xp)上,父进程将不会完成,直到longtask.py完成其工作。 这不是你想要的CGI脚本。 这个问题不是特定于Python,在PHP社区中,问题是相同的。

解决方案是在win API中将DETACHED_PROCESS 进程创建标志传递给底层的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中开始后台进程的搜索并没有任何亮点。




Links