shell:用Python调用外部命令

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


看看标准库中的子进程模块:

1
2
import subprocess
subprocess.run(["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使用调用:

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


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

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

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

然而,虽然这很方便,但是您必须手动处理shell字符的转义,比如空格等。另一方面,这也允许您运行简单的shell命令,而不是实际的外部程序。见文档。

stream = os.popen("some_command with args")将执行与os.system相同的操作,只是它提供了一个类似文件的对象,您可以使用该对象访问该流程的标准输入/输出。popen还有其他3种变体,它们处理i/o的方式略有不同。如果您将所有内容都作为字符串传递,那么您的命令将传递到shell;如果您将它们作为列表传递,那么您就不需要担心转义任何内容。见文档。

subprocess模块的Popen类。这是作为os.popen的替代品,但是由于它的全面,它的缺点是稍微复杂一些。例如,你可以说:

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

而不是:

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

但是把所有的选项放在一个统一的类中而不是四个不同的popen函数中是很好的。见文档。

来自subprocess模块的call函数。这基本上就像Popen类一样,接受所有相同的参数,但是它只是等待命令完成并给出返回代码。例如:

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

见文档。

如果您使用的是Python 3.5或更高版本,您可以使用新的subprocess.run函数,它与上面的函数非常相似,但是更加灵活,并且在命令执行完毕时返回一个CompletedProcess对象。

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

subprocess模块应该是您使用的模块。

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

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

想象一下,用户输入"我妈妈不爱我"。rm射频/"。


我通常使用:

1
2
3
4
5
6
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脚本执行进程活得更长。

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

1
2
3
4
5
6
7
8
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模块导入这个标志,否则你应该自己定义它:

1
2
3
4
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。有效的解决方案如下:

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

我没有在其他平台上检查过代码,也不知道freebsd上行为的原因。如果有人知道,请分享你的想法。在Python中搜索启动后台进程还没有给出任何答案。


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

如果希望返回命令的结果,可以使用os.popen。但是,从2.6版开始就不赞成使用这个方法,而是支持子流程模块,其他的答案已经很好地涵盖了这个模块。


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

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


1
2
import os
os.system("your command")

注意,这很危险,因为命令没有被清除。关于"os"和"sys"模块的相关文档,我将留给您谷歌。有很多函数(exec*和spawn*)可以做类似的事情。


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

Sources:

子流程:https://docs.python.org/3.5/library/subprocess.htmlshlex: https://docs.python.org/3/library/shlex.html操作系统:https://docs.python.org/3.5/library/os.html承宪:https://amoffat.github.io/sh/铅:https://plumbum.readthedocs.io/en/latest/pexpect: https://pexpect.readthedocs.io/en/stable/面料:http://www.fabfile.org/特使:https://github.com/kennethreitz/envoy命令:https://docs.python.org/2/library/commands.html

These are all the libraries:

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

subprocess

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

1
2
3
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的外部命令(注意:还有一个子进程.popen)。os总是运行shell,对于不需要或者不知道如何使用subprocess.run的人来说,这是一个简单的选择。

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

sh

sh是一个子进程接口,它允许您像调用函数一样调用程序。如果您希望多次运行命令,这将非常有用。

1
2
3
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中那样调用函数。如果您想要运行没有外壳的管道,铅是非常有用的。

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

pexpect

pexpect允许派生子应用程序,控制它们并在它们的输出中找到模式。对于希望在Unix上使用tty的命令,这是子进程的更好选择。

1
2
3
4
pexpect.run("ls -l") # Run command as normal
child = pexpect.spawn('scp foo [email protected]:.') # Spawns child application
child.expect('Password:') # When this is the output
child.sendline('mypassword')

fabric

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

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

envoy

特使被称为"人类的子进程"。它被用作subprocess模块的便利包装器。

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

commands

commands包含用于os.popen的包装器函数,但是由于subprocess是更好的选择,所以已经从python3中删除了它。

编辑是基于j·f·塞巴斯蒂安的评论。


我总是使用fabric来做这样的事情:

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

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

看一个例子:

1
2
3
4
from sh import vgdisplay
print vgdisplay()
print vgdisplay('-v')
print vgdisplay(v=True)

也可以检查"pexpect"Python库。

它允许交互控制外部程序/命令,甚至ssh, ftp, telnet等。你可以这样输入:

1
2
3
4
5
6
7
child = pexpect.spawn('ftp 192.168.0.24')

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

child.sendline('anonymous')

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

如果需要调用的命令的输出,然后可以使用子进程。check_output (Python 2.7 +)。

1
2
3
>>> subprocess.check_output(["ls","-l","/dev/null"])
'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null
'

还要注意shell参数。

If shell is True, the specified command will be executed through the shell. This can be useful if you are using Python primarily for the enhanced control flow it offers over most system shells and still want convenient access to other shell features such as shell pipes, filename wildcards, environment variable expansion, and expansion of ~ to a user’s home directory. However, note that Python itself offers implementations of many shell-like features (in particular, glob, fnmatch, os.walk(), os.path.expandvars(), os.path.expanduser(), and shutil).


与标准库

的使用子进程模块(python3):

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

这是推荐的标准方法。然而,更复杂的任务(管道、输出、输入等)的构造和编写可能非常单调乏味。

关于Python版本的注意事项:如果您仍然在使用python2,子进程。call的工作原理类似。

ProTip: shlex。split可以帮助您解析runcall和其他subprocess函数的命令,以防您不想(或不能)以列表的形式提供它们:

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

与外部依赖关系

如果您不介意外部依赖,请使用铅:

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

它是最好的subprocess包装。它是跨平台的,也就是说,它可以在Windows和类unix系统上工作。安装由pip install plumbum

另一个受欢迎的图书馆是sh:

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

但是,sh取消了对Windows的支持,所以它不像以前那么棒了。安装由pip install sh


这就是我运行命令的方式。这段代码包含了您需要的所有内容

1
2
3
4
5
6
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版本的兼容性,那么从Python 3.5开始推荐使用subprocess.run。它更加一致,并提供了类似的使节的易用性。(不过管道并不那么简单。看看这个问题。)

下面是文档中的一些例子。

运行流程:

1
2
>>> subprocess.run(["ls","-l"])  # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

失败运行时的提升:

1
2
3
4
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

捕获输出:

1
2
3
4
>>> 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
'
)

原始答:

我建议试试特使。它是子进程的包装器,子进程的目标是替换旧的模块和函数。特使是人类的子进程。

自述文件中的示例用法:

1
2
3
4
5
6
7
8
>>> 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
''

管道周围的东西:

1
2
3
4
5
6
7
8
9
>>> r = envoy.run('uptime | pbcopy')

>>> r.command
'pbcopy'
>>> r.status_code
0

>>> r.history
[<Response 'uptime'>]


不输出结果:

1
2
import os
os.system("your command here")

输出结果:

1
2
3
4
import commands
commands.getoutput("your command here")
or
commands.getstatusoutput("your command here")


https://docs.python.org/2/library/subprocess.html

…或者一个非常简单的命令:

1
2
import os
os.system('cat testfile')

还有铅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> from plumbum import local
>>> ls = local["ls"]
>>> ls
LocalCommand(<LocalPath /bin/ls>)
>>> ls()
u'build.py
dist
docs
LICENSE
plumbum
README.rst
setup.py
tests
todo.txt
'

>>> notepad = local["c:\\windows\
otepad.exe"
]
>>> notepad()                                   # Notepad window pops up
u''                                             # Notepad window is closed by user, command returns

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

更多信息请点击这里。


Calling an external command in Python

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

1
2
3
4
5
>>> 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开始,文档推荐子进程。

The recommended approach to invoking subprocesses is to use the run() function for all use cases it can handle. For more advanced use cases, the underlying Popen interface can be used directly.

下面是一个最简单的用法的例子——它完全按照要求做了:

1
2
3
4
5
>>> 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。

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

1
2
3
4
>>> completed_process.args
'python --version'
>>> completed_process.returncode
0

捕获输出

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

1
2
3
4
5
6
7
8
9
>>> cp = subprocess.run('python --version',
                        stderr=subprocess.PIPE,
                        stdout=subprocess.PIPE)
>>> cp.stderr
b'Python 3.6.1 :: Anaconda 4.4.0 (64-bit)

'

>>> cp.stdout
b''

(我发现把版本信息放到stderr而不是stdout中很有趣,而且有点违反直觉。)

传递一个命令列表

可以很容易地从手动提供命令字符串(如问题所示)过渡到通过编程构建字符串。不要以编程方式构建字符串。这是一个潜在的安全问题。最好假设您不信任输入。

1
2
3
4
5
6
7
8
9
>>> import textwrap
>>> args = ['python', textwrap.__file__]
>>> cp = subprocess.run(args, stdout=subprocess.PIPE)
>>> cp.stdout
b'Hello there.

  This is indented.

'

注意,只有args应该被定位传递。

完整签名

下面是源代码中的实际签名,如help(run)所示:

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

popenargskwargs被提供给Popen构造函数。input可以是一个字节字符串(或unicode,如果指定编码或universal_newlines=True),它将通过管道传输到子进程的stdin。

文件比我能更好地描述timeout=check=True:

The timeout argument is passed to Popen.communicate(). If the timeout
expires, the child process will be killed and waited for. The
TimeoutExpired exception will be re-raised after the child process has
terminated.

If check is true, and the process exits with a non-zero exit code, a
CalledProcessError exception will be raised. Attributes of that
exception hold the arguments, the exit code, and stdout and stderr if
they were captured.

这个关于check=True的例子比我能想到的更好:

1
2
3
4
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

扩展签名

下面是一个扩展的签名,如文档所示:

1
2
3
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None,
shell=False, cwd=None, timeout=None, check=False, encoding=None,
errors=None)

注意,这表明只应该以位置传递args列表。因此,将剩余的参数作为关键字参数传递。

Popen

当使用Popen代替?我很难仅根据这些参数就找到用例。但是,直接使用Popen可以让您访问它的方法,包括poll、'send_signal'、'terminate'和'wait'。

下面是源代码中给出的Popen签名。我认为这是对信息最精确的封装(与help(Popen)相反):

1
2
3
4
5
6
7
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文件提供了更多的资料:

1
2
3
4
5
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)

Execute a child program in a new process. On POSIX, the class uses
os.execvp()-like behavior to execute the child program. On Windows,
the class uses the Windows CreateProcess() function. The arguments to
Popen are as follows.

理解关于Popen的其余文档将留给读者作为练习。


使用:

1
2
3
4
5
import os

cmd = 'ls -al'

os.system(cmd)

这个模块提供了一种使用操作系统相关功能的可移植方法。

对于更多的os函数,这里是文档。


事情可以这么简单:

1
2
3
import os
cmd ="your command"
os.system(cmd)


我倾向于使用子进程与shlex(处理转义的引用字符串):

1
2
3
4
5
6
>>> import subprocess, shlex
>>> command = 'ls -l"/your/path/with spaces/"'
>>> call_params = shlex.split(command)
>>> print call_params
["ls","-l","/your/path/with spaces/"]
>>> subprocess.call(call_params)

如果您不想测试返回值,则可以使用subprocess.check_call。它会对任何错误抛出异常。


os.system不允许您存储结果,因此,如果您想将结果存储在某个列表中,或者subprocess.call工作的某个东西中。


这里还有一个以前没有提到的不同之处。

subprocess.Popen作为子进程执行<命令>。在我的例子中,我需要执行需要与另一个程序通信的文件。

我尝试了子流程,并且执行成功。但是无法沟通。当我从终端运行这两个程序时,一切都是正常的。

一个:(注意:kwrite的行为与其他应用程序不同。如果你用火狐浏览器试试下面的功能,结果会不一样。)

如果您尝试os.system("kwrite"),程序流将冻结,直到用户关闭kwrite。为了克服这一点,我转而尝试os.system(konsole -e kwrite)。这一次程序继续运行,但是kwrite变成了控制台的子进程。

任何运行kwrite的人都不是子进程(例如,在系统监视器中,它必须出现在树的最左边)。


使用os模块

1
2
import os
os.system("your command")

1
2
import os
os.system("ifconfig")


我非常喜欢shell_command,因为它很简单。它构建在子流程模块之上。

下面是文档中的一个例子:

1
2
3
4
5
6
7
8
9
>>> from shell_command import shell_call
>>> shell_call("ls *.py")
setup.py  shell_command.py  test_shell_command.py
0
>>> shell_call("ls -l *.py")
-rw-r--r-- 1 ncoghlan ncoghlan  391 2011-12-11 12:07 setup.py
-rw-r--r-- 1 ncoghlan ncoghlan 7855 2011-12-11 16:16 shell_command.py
-rwxr-xr-x 1 ncoghlan ncoghlan 8463 2011-12-11 16:17 test_shell_command.py
0

无耻的插件,我为这个写了一个库:Phttps://github.com/houqp/shell.py

现在它基本上是popen和shlex的包装。它还支持管道命令,因此您可以在Python中更容易地链接命令。你可以这样做:

1
ex('echo hello shell.py') |"awk '{print $2}'"

在Linux下,如果您想调用一个独立执行的外部命令(在python脚本终止后继续运行),您可以使用一个简单的队列作为任务假脱机程序或at命令

任务假脱机程序的一个例子:

1
2
import os
os.system('ts <your-command>')

关于任务假脱机程序的说明(ts):

您可以使用以下命令设置要运行的并发进程数量("slot"):

ts -S

安装ts不需要管理特权。您可以用一个简单的make从源代码下载并编译它,然后将它添加到您的路径中,这样就完成了。


在Windows中,你只需要导入subprocess模块,调用subprocess.Popen()subprocess.Popen().communicate()subprocess.Popen().wait()就可以运行外部命令,如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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:
"
, execute(command)

输出:

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

你可以使用Popen,然后你可以检查程序的状态:

1
2
3
4
5
from subprocess import Popen

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

查看subprocess.Popen。


从openstack中子中提取网络id:

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

新星网络列表的输出

1
2
3
4
5
6
7
8
+--------------------------------------+------------+------+
| 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)

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


以下是我的观点:在我看来,这是处理外部命令的最佳实践……

这些是execute方法的返回值…

1
pass, stdout, stderr = execute(["ls","-la"],"/home/user/desktop")

这是执行方法…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def execute(cmdArray,workingDir):

    stdout = ''
    stderr = ''

    try:
        try:
            process = subprocess.Popen(cmdArray,cwd=workingDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
        except OSError:
            return [False, '', 'ERROR : command(' + ' '.join(cmdArray) + ') could not get executed!']

        for line in iter(process.stdout.readline, b''):

            try:
                echoLine = line.decode("utf-8")
            except:
                echoLine = str(line)

            stdout += echoLine

        for line in iter(process.stderr.readline, b''):

            try:
                echoLine = line.decode("utf-8")
            except:
                echoLine = str(line)

            stderr += echoLine

    except (KeyboardInterrupt,SystemExit) as err:
        return [False,'',str(err)]

    process.stdout.close()

    returnCode = process.wait()
    if returnCode != 0 or stderr != '':
        return [False, stdout, stderr]
    else:
        return [True, stdout, stderr]


非常简单的方式运行任何命令,并得到结果:

1
2
3
4
5
6
from commands import getstatusoutput

try:
    return getstatusoutput("ls -ltr")
except Exception, e:
    return None


调用是一个Python(2.7和3.4+)任务执行工具&图书馆。它为运行shell命令提供了一个干净的高级API

1
2
3
4
5
6
7
>>> from invoke import run
>>> cmd ="pip install -r requirements.txt"
>>> result = run(cmd, hide=True, warn=True)
>>> print(result.ok)
True
>>> print(result.stdout.splitlines()[-1])
Successfully installed invocations-0.13.0 pep8-1.5.7 spec-1.3.1


通常,我对外部命令使用以下函数,这对于长时间运行的进程尤其方便。下面的方法在进程运行时跟踪进程输出并返回输出,如果进程失败则引发异常。

如果进程是使用进程上的poll()方法完成的,则会出现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import subprocess,sys

def exec_long_running_proc(command, args):
    cmd ="{} {}".format(command,"".join(str(arg) if ' ' not in arg else arg.replace(' ','\ ') for arg in args))
    print(cmd)
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # Poll process for new output until finished
    while True:
        nextline = process.stdout.readline().decode('UTF-8')
        if nextline == '' and process.poll() is not None:
            break
        sys.stdout.write(nextline)
        sys.stdout.flush()

    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise Exception(command, exitCode, output)

您可以这样调用它:

1
exec_long_running_proc(command ="hive", args=["-f", hql_path])


这里调用一个外部命令并返回或打印命令的输出:

Python子进程check_output非常适合

Run command with arguments and return its output as a byte string.

1
2
3
import subprocess
proc = subprocess.check_output('ipconfig /all')
print proc


使用:

1
2
3
4
5
import subprocess

p = subprocess.Popen("df -h", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
print p.split("
"
)

它提供了很好的输出,更容易工作:

1
2
3
4
5
6
7
8
9
10
['Filesystem      Size  Used Avail Use% Mounted on',
 '/dev/sda6        32G   21G   11G  67% /',
 'none            4.0K     0  4.0K   0% /sys/fs/cgroup',
 'udev            1.9G  4.0K  1.9G   1% /dev',
 'tmpfs           387M  1.4M  386M   1% /run',
 'none            5.0M     0  5.0M   0% /run/lock',
 'none            1.9G   58M  1.9G   3% /run/shm',
 'none            100M   32K  100M   1% /run/user',
 '/dev/sda5       340G  222G  100G  69% /home',
 '']

为了进一步讨论,如果您包括使用Python控制台,您可以从IPython调用外部命令。在IPython提示符中,可以通过前缀'!'调用shell命令。您还可以将Python代码与shell结合起来,并将shell脚本的输出分配给Python变量。

例如:

1
2
3
4
5
6
7
In [9]: mylist = !ls

In [10]: mylist
Out[10]:
['file1',
 'file2',
 'file3',]

例如(在Linux中):

1
2
import subprocess
subprocess.run('mkdir test.dir', shell=True)

这将创建测试。当前目录中的目录。注意,这也可以:

1
2
import subprocess
subprocess.call('mkdir test.dir', shell=True)

使用os的等效代码。系统:

1
2
import os
os.system('mkdir test.dir')

最佳实践是使用子进程而不是os, .run优先于.call。所有您需要了解的关于子流程的信息都在这里。另外,请注意所有Python文档都可以从这里下载。我下载了打包为.zip的PDF文件。我提到这一点是因为在tutorial.pdf(81页)中有一个很好的os模块概述。此外,它还是Python程序员的权威资源。


在Python中运行外部命令有很多不同的方法,它们都有各自的优点和缺点。

我和我的同事一直在编写Python系统管理工具,所以我们需要运行许多外部命令,有时您希望它们阻塞或异步运行、超时、每秒更新等等。

也有不同的方法来处理返回代码和错误,您可能想要解析输出,并提供新的输入(以expect类型的样式)。或者,您将需要重定向stdin、stdout和stderr,以在不同的tty中运行(例如,在使用screen时)。

因此,您可能需要围绕外部命令编写许多包装器。这是我们写的Python模块,它可以处理几乎任何你想要的,如果不是,它是非常灵活的,所以你可以很容易地扩展它:

https://github.com/hpcugent/vsc-base/blob/master/lib/vsc/utils/run.py


使用Python执行shell命令有两种主要的方式。下面提到的两个示例都展示了如何使用Python获得当前工作目录的名称(pwd)。您可以使用任何其他Unix命令来代替pwd

1.第一种方法:可以使用python中的os模块和system()函数来执行python中的shell命令。

1
2
import os
os.system('pwd')

输出:

1
/Users/siddharth

1.第二种方法:另一种方法是使用子进程模块和call()函数。

1
2
import subprocess
subprocess.call('pwd')

输出:

1
/Users/siddharth

用Python调用外部命令

调用外部命令的一种简单方法是使用os.system(...)。这个函数返回命令的exit值。但缺点是我们不会得到stdout和stderr。

1
2
3
ret = os.system('some_cmd.sh')
if ret != 0 :
    print 'some_cmd.sh execution returned failure'

在后台用Python调用外部命令

subprocess.Popen为运行外部命令提供了比使用os.system更大的灵活性。我们可以在后台启动一个命令,然后等待它完成。然后我们可以得到stdout和stderr。

1
2
3
4
5
6
proc = subprocess.Popen(["./some_cmd.sh"], stdout=subprocess.PIPE)
print 'waiting for ' + str(proc.pid)
proc.wait()
print 'some_cmd.sh execution finished'
(out, err) = proc.communicate()
print 'some_cmd.sh output : ' + out

在后台调用一个长时间运行的Python外部命令,并在一段时间后停止

我们甚至可以在后台使用subprocess.Popen启动一个长时间运行的进程,并在某个时间完成任务后杀死它。

1
2
3
4
5
proc = subprocess.Popen(["./some_long_run_cmd.sh"], stdout=subprocess.PIPE)
# Do something else
# Now some_long_run_cmd.sh exeuction is no longer needed, so kill it
os.system('kill -15 ' + str(proc.pid))
print 'Output : ' proc.communicate()[0]

对于Python 3.5+,建议使用子进程模块中的run函数。这将返回一个CompletedProcess对象,您可以从该对象轻松地获得输出和返回代码。

1
2
3
4
5
from subprocess import PIPE, run

command = ['echo', 'hello']
result = run(command, stdout=PIPE, stderr=PIPE, universal_newlines=True)
print(result.returncode, result.stdout, result.stderr)


使用subprocess Python模块的Popen函数是运行Linux命令的最简单方法。在这种情况下,Popen.communicate()函数将输出您的命令。例如

1
2
3
4
5
6
import subprocess

..
process = subprocess.Popen(..)   # Pass command and arguments to the function
stdout, stderr = process.communicate()   # Get command output and error
..


使用subprocess.call:

1
2
3
4
5
6
7
from subprocess import call

# using list
call(["echo","Hello","world"])

# single string argument varies across platforms so better split it
call("echo Hello world".split(""))

一个简单的方法是使用os模块:

1
2
import os
os.system('ls')

或者,您也可以使用子流程模块

1
2
import subprocess
subprocess.check_call('ls')

如果你想将结果存储在一个变量中,请尝试:

1
2
import subprocess
r = subprocess.check_output('ls')

如果需要从Python笔记型计算机(如Jupyter、Zeppelin、Databricks或谷歌Cloud Datalab)调用shell命令,可以使用!前缀。

例如,

1
!ls -ilF

调用命令的方法有很多。

例如:

如果and.exe需要两个参数。在cmd中我们可以调用sample.exe使用这个:and.exe 2 3,它在屏幕上显示5

如果我们使用Python脚本来调用and.exe,我们应该像..

os.system(cmd,...)

os.system(("and.exe" +"" +"2" +"" +"3"))

os.popen(cmd,...)

os.popen(("and.exe" +"" +"2" +"" +"3"))subprocess.Popen(cmd,...)subprocess.Popen(("and.exe" +"" +"2" +"" +"3"))

这太难了,所以我们可以用一个空格加入cmd:

1
2
3
import os
cmd ="".join(exename,parameters)
os.popen(cmd)


我写了一个小库来帮助这个用例:

https://pypi.org/project/citizenshell/

它可以安装使用

1
pip install citizenshell

然后使用如下:

1
2
from citizenshell import sh
assert sh("echo Hello World") =="Hello World"

您可以从stderr中分离出stdout,并提取退出代码如下:

1
2
3
4
result = sh(">&amp;2 echo error &amp;&amp; echo output &amp;&amp; exit 13")
assert result.stdout() == ["output"]
assert result.stderr() == ["error"]
assert result.exit_code() == 13

更酷的是,你不必等到底层shell退出后才开始处理输出:

1
2
for line in sh("for i in 1 2 3 4; do echo -n 'It is '; date +%H:%M:%S; sleep 1; done", wait=False)
    print">>>", line +"!"

将打印行,因为他们是可用的感谢等待=False

1
2
3
4
>>> It is 14:24:52!
>>> It is 14:24:53!
>>> It is 14:24:54!
>>> It is 14:24:55!

更多的例子可以在https://github.com/meuter/citizenshell找到


经过一些研究,我得到了以下代码,这对我来说非常有用。它基本上同时打印stdout和stderr。希望它能帮助到其他需要它的人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
stdout_result = 1
stderr_result = 1


def stdout_thread(pipe):
    global stdout_result
    while True:
        out = pipe.stdout.read(1)
        stdout_result = pipe.poll()
        if out == '' and stdout_result is not None:
            break

        if out != '':
            sys.stdout.write(out)
            sys.stdout.flush()


def stderr_thread(pipe):
    global stderr_result
    while True:
        err = pipe.stderr.read(1)
        stderr_result = pipe.poll()
        if err == '' and stderr_result is not None:
            break

        if err != '':
            sys.stdout.write(err)
            sys.stdout.flush()


def exec_command(command, cwd=None):
    if cwd is not None:
        print '[' + ' '.join(command) + '] in ' + cwd
    else:
        print '[' + ' '.join(command) + ']'

    p = subprocess.Popen(
        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd
    )

    out_thread = threading.Thread(name='stdout_thread', target=stdout_thread, args=(p,))
    err_thread = threading.Thread(name='stderr_thread', target=stderr_thread, args=(p,))

    err_thread.start()
    out_thread.start()

    out_thread.join()
    err_thread.join()

    return stdout_result + stderr_result


我推荐以下方法'run',它将帮助我们获得STDOUT, STDERR和退出状态作为字典;调用此函数的人可以通过"run"方法读取返回的字典,以了解进程的实际状态。

1
2
3
4
5
6
7
8
9
10
11
12
  def run (cmd):
       print"+ DEBUG exec({0})".format(cmd)
       p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
       (out, err) = p.communicate()
       ret        = p.wait()
       out        = filter(None, out.split('
'
))
       err        = filter(None, err.split('
'
))
       ret        = True if ret == 0 else False
       return dict({'output': out, 'error': err, 'status': ret})
  #end


Eli上面描述的子进程模块非常强大,但是用于进行博格标准系统调用并检查其输出的语法不必要地冗长。

进行系统调用的最简单方法是使用commands模块(仅限Linux)。

1
2
3
> import commands
> commands.getstatusoutput("grep matter alice-in-wonderland.txt")
(0,"'Then it doesn't matter which way you go,' said the Cat.")

元组中的第一项是流程的返回代码。第二项是它的标准输出(和标准错误,合并)。

Python开发人员已经"弃用"了commands模块,但这并不意味着您不应该使用它。只是他们不再开发它了,这没关系,因为它已经很完美了(在它的小而重要的功能上)。


由于其中一些答案与以前的python版本有关,或者使用了os.system模块,所以我将此答案发布给像我这样打算在python 3.5+中使用subprocess的人。下面的代码在Linux上对我很有用:

1
2
3
4
5
import subprocess

#subprocess.run() returns a completed process object that can be inspected
c = subprocess.run(["ls","-ltrh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(c.stdout.decode('utf-8'))

正如在文档中提到的,PIPE值是字节序列,为了正确地显示它们,应该考虑解码。对于python的后续版本,text=Trueencoding='utf-8'被添加到subprocess.run()的kwarg中。

上述代码的输出为:

1
2
3
4
5
total 113M
-rwxr-xr-x  1 farzad farzad  307 Jan 15  2018 vpnscript
-rwxrwxr-x  1 farzad farzad  204 Jan 15  2018 ex
drwxrwxr-x  4 farzad farzad 4.0K Jan 22  2018 scripts
.... # some other lines

我编写了一个包装器来处理错误和重定向输出等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import shlex
import psutil
import subprocess

def call_cmd(cmd, stdout=sys.stdout, quiet=False, shell=False, raise_exceptions=True, use_shlex=True, timeout=None):
   """Exec command by command line like 'ln -ls"/var/log"'
   """

    if not quiet:
        print("Run %s", str(cmd))
    if use_shlex and isinstance(cmd, (str, unicode)):
        cmd = shlex.split(cmd)
    if timeout is None:
        process = subprocess.Popen(cmd, stdout=stdout, stderr=sys.stderr, shell=shell)
        retcode = process.wait()
    else:
        process = subprocess.Popen(cmd, stdout=stdout, stderr=sys.stderr, shell=shell)
        p = psutil.Process(process.pid)
        finish, alive = psutil.wait_procs([p], timeout)
        if len(alive) > 0:
            ps = p.children()
            ps.insert(0, p)
            print('waiting for timeout again due to child process check')
            finish, alive = psutil.wait_procs(ps, 0)
        if len(alive) > 0:
            print('process {} will be killed'.format([p.pid for p in alive]))
            for p in alive:
                p.kill()
            if raise_exceptions:
                print('External program timeout at {} {}'.format(timeout, cmd))
                raise CalledProcessTimeout(1, cmd)
        retcode = process.wait()
    if retcode and raise_exceptions:
        print("External program failed %s", str(cmd))
        raise subprocess.CalledProcessError(retcode, cmd)

你可以这样称呼它:

1
2
3
cmd = 'ln -ls"/var/log"'
stdout = 'out.txt'
call_cmd(cmd, stdout)

如果您没有在命令中使用用户输入,您可以使用这个

1
2
3
4
5
6
from os import getcwd
from subprocess import check_output
from shlex import quote

def sh(command):
    return check_output(quote(command), shell=True, cwd=getcwd(), universal_newlines=True).strip()

把它当做

1
branch = sh('git rev-parse --abbrev-ref HEAD')

shell=True将生成一个shell,因此您可以使用管道和诸如此类的shell (sh('ps aux | grep python'))。这对于运行硬编码命令和处理输出非常非常方便。universal_lines=True确保以字符串形式返回输出,而不是二进制形式。

cwd=getcwd()将确保使用与解释器相同的工作目录运行该命令。这对于git命令的工作很方便,就像上面的git分支名称示例一样。

一些食谱

可用内存(以兆为单位):sh('free -m').split('
')[1].split()[1]
可用空间on / in % sh('df -m /').split('
')[1].split()[4][0:-1]
cpu负载sum(map(float, sh('ps -ef -o pcpu').split('
')[1:])

但这对用户输入不安全,从文档:

Security Considerations?

Unlike some other popen functions, this implementation will never
implicitly call a system shell. This means that all characters,
including shell metacharacters, can safely be passed to child
processes. If the shell is invoked explicitly, via shell=True, it is
the application’s responsibility to ensure that all whitespace and
metacharacters are quoted appropriately to avoid shell injection
vulnerabilities.

When using shell=True, the shlex.quote() function can be used to
properly escape whitespace and shell metacharacters in strings that
are going to be used to construct shell commands.

在使用shell命令的用户输入时,即使使用shlex.quote()也可以让您保持一点偏执。一个选项是使用硬代码命令获取一些通用输出并根据用户输入进行过滤。无论如何,使用shell=False将确保只执行您想要执行的进程,否则您将得到一个No such file or directory错误。

shell=True也有一些性能影响,从我的测试来看,它似乎比shell=False(默认值)慢20%左右。

1
2
3
4
5
In [50]: timeit("check_output('ls -l'.split(), universal_newlines=True)", number=1000, globals=globals())
Out[50]: 2.6801227919995654

In [51]: timeit("check_output('ls -l', universal_newlines=True, shell=True)", number=1000, globals=globals())
Out[51]: 3.243950183999914