调用系统命令之subprocess模块

除了常见的os.system和os.popen方法,官方强烈推荐使用subprocess来调用系统命令。

这个库用起来其实很简单,按照惯例先贴一下官文关键点:

The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes.

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.

推荐的使用方式是:任何场景下,只要调用run()方法即可,已经封装好了一切。
底层接口Popen也可以直接使用,不过要注意管道堵塞问题。

API说明如下:

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, ...)

Run the command described by args. Wait for command to complete, then return a CompletedProcess instance. 执行命令,等待执行完成,返回CompletedProcess实例
args is required for all calls and should be a string, or a sequence of program arguments.
If passing a single string, shell must be True.
If shell is True, the specified command will be executed through the shell.
On POSIX with shell=True, the shell defaults to /bin/sh. 默认shell是/bin/sh
subprocess.PIPE
Special value that can be used as the stdin, stdout or stderr argument to Popen and indicates that a pipe to the standard stream should be opened.

subprocess.STDOUT
Special value that can be used as the stderr argument to Popen and indicates that standard error should go into the same handle as standard output.
合并错误输出到标准输出
class subprocess.CompletedProcess

The return value from run(), representing a process that has finished.

args
The arguments used to launch the process. This may be a list or a string.

returncode
Exit status of the child process. Typically, an exit status of 0 indicates that it ran successfully.
A negative value -N indicates that the child was terminated by signal N (POSIX only).

stdout
Captured stdout from the child process. A bytes sequence, or a string if run() was called with an encoding, errors, or text=True.

看不懂英文的同学可以略过API说明,下面来说使用姿势。

这里提供两种传参方式,一种是将要执行的命令以一整个字符串传入,一种是以序列方式传入,具体来说是这样:

subprocess.run(["ls", "-l"])
subprocess.run('ls -l', shell=True)
r = subprocess.run('ls -l', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
print(r)
print(type(r))
print(r.stdout.decode()) # 获取结果
print(r.returncode)

仅此而已,就这么简单。


关于Popen,简单说几句。

This will deadlock when using stdout=PIPE or stderr=PIPE and the child process generates enough output to a pipe,
such that it blocks waiting for the OS pipe buffer to accept more data. Use Popen.communicate() when using pipes to avoid that.

官文说Popen中使用PIPE可能会造成堵塞,建议使用Popen.communicate()来避免这个问题。
这一点,run()方法已经封装解决了,直接使用run()方法可以不理会这个问题。

解决这个问题还有一个思路,就是不用PIPE。使用临时文件保存输出。

from subprocess import Popen
from tempfile import TemporaryFile

with TemporaryFile(mode='w+b') as f:  # 使用临时文件保存输出结果,避免死锁
    with Popen('ifconfig', shell=True, stdout=f, stderr=subprocess.STDOUT) as proc:
        status = proc.wait()
        f.seek(0)  # 写文件时指针在文末所以读取文件需要移动指针
        print(f.read().decode())
        print(status)

另外,这个模块里面还有一个老方法可以用来执行命令。

implicitly invoke the system shell.

subprocess.getoutput(cmd)
Return output (stdout and stderr) of executing cmd in a shell.
r = subprocess.getoutput('ls -l')
print(r)
print(type(r))
# <class 'str'>

一句话,请使用subprocess.run()方法,其它可以忽略。

参考:
https://docs.python.org/3/library/subprocess.html
https://docs.python.org/3/library/tempfile.html

posted @ 2018-12-27 15:07  KeithTt  阅读(265)  评论(0编辑  收藏  举报