Python 内置模块之 subprocess
0、背景
在用python编程时,我们经常会需要调用外部命令,比如用调用一些 Linux 的命令。这个时候就可以用到subprocess这个模块了。
subprocess 模块是 python 从2.4版本开始引入的模块。主要用来取代一些旧的模块方法,如os.system、os.spawn、os.popen、commands.*等。subprocess通过子进程来执行外部指令,并通过input/output/error管道,获取子进程的执行的返回信息。
1、介绍
subprocess模块可以生成新的进程,连接到它们的input/output/error管道,同时获取它们的返回码。
2、写在前面,因为参数 shell
和 arg
有坑
2.1、重要参数 shell
和 arg
, 不但重要而且有坑
args
:表示要执行的命令。args
参数可以接收一个类似du -sh
的字符串。要执行的程序就是字符串本身args
参数也可以传递一个类似['du', '-sh']
的字符串分割列表。要执行的程序一般就是这个列表的第一项。
shell
:如果该参数为True,将通过操作系统的shell执行指定的命令shell=True
时如果args是个字符串类,则调用shell去执行这个字符串。
如果args是个列表,则第一项被视为命令,其余的都视为是给 shell本身 的参数。这会导致在 linux 和 windows 下面会有不同的后果
Linux:等效于:
subprocess.Popen(['/bin/sh', '-c', args[0], args[1], ...])
Windows:
subprocess.Popen(["notepad.exe", "test.txt"])
,subprocess.Popen("notepad.exe test.txt")
效果一样。
这是由于windows下的api函数CreateProcess接受的是一个字符串。即使是列表形式的参数,也需要先合并成字符串再传递给api函数1
2
3subprocess.Popen(["notepad.exe", "test.txt"]shell=True)
等同于
subprocess.Popen("cmd.exe /C "+"notepad.exe test.txt" shell=True)
shell=False
时,subprocess.Popen只接受数组变量作为命令,并将数组的第一个元素作为命令,剩下的全部作为 该命令 的参数。- 在 Unix下,当shell=False(默认)时,Popen使用os.execvp()来执行子程序。
通常subprocess 会和 shlex 一块使用。shlex.split()可以被用于序列化复杂的命令参数
1 |
|
2.2、其他参数
check
:check为True时,表示执行命令的进程以非0状态码退出时会抛出;subprocess.CalledProcessError异常;check为False时,状态码为非0退出时不会抛出异常;bufsize
: 如果指定了bufsize参数,作用就和内建函数open()一样:- 0表示不缓冲,默认是0。
- 1表示行缓冲
- 其他正数表示近似的缓冲区字节数,负数表示使用系统默认值。
executable
: 指定要执行的程序。它很少会被用到:一般程序可以由args 参数指定。如果shell=True ,executable 可以用于指定用哪个shell来执行(比如bash、csh、zsh等)。- *nix下,默认是 /bin/sh。
- windows下,就是环境变量 COMSPEC 的值。windows下,只有当你要执行的命令确实是shell内建命令(比如dir ,copy 等)时,你才需要指定shell=True,而当你要执行一个基于命令行的批处理脚本的时候,不需要指定此项。
stdin stdout和stderr:分别表示子程序的标准输入、标准输出和标准错误。可选的值有PIPE或者一个有效的文件描述符(其实是个正整数)或者一个文件对象,还有None。
- 如果是PIPE,则表示需要创建一个新的管道。
- 如果是None,不会做任何重定向工作,子进程的文件描述符会继承父进程的。
- 另外,stderr的值还可以是STDOUT,表示子进程的标准错误也输出到标准输出。
preexec_fn参数:如果把preexec_fn设置为一个可调用的对象(比如函数),就会在子进程被执行前被调用。(仅限*nix)
close_fds参数:如果把close_fds设置成True,*nix下会在开子进程前把除了0、1、2以外的文件描述符都先关闭。在 Windows下也不会继承其他文件描述符。
cwd参数:如果cwd不是None,则会把cwd做为子程序的当前目录。注意,并不会把该目录做为可执行文件的搜索目录,所以不要把程序文件所在目录设置为cwd 。
env参数:如果env不是None,则子程序的环境变量由env的值来设置,而不是默认那样继承父进程的环境变量。注意,即使你只在env里定义了某一个环境变量的值,也会阻止子程序得到其他的父进程的环境变量(也就是说,如果env里只有1项,那么子进程的环境变量就只有1个了)。例如:
1
2
3
4>>> subprocess.Popen('env', env={'test':'123', 'testtext':'zzz'})
test=123
testtext=zzzuniversal_newlines参数:如果把universal_newlines 设置成True,则子进程的stdout和stderr被视为文本对象,并且不管是*nix的行结束符(’/n’ ),还是老mac格式的行结束符(’/r’ ),还是windows 格式的行结束符(’/r/n’ )都将被视为 ‘/n’ 。
startupinfo和creationflags参数:如果指定了startupinfo和creationflags,将会被传递给后面的CreateProcess()函数,用于指定子程序的各种其他属性,比如主窗口样式或者是子进程的优先级等。(仅限Windows)
3、基本操作方法
3.1、subprocess 运行命令的方法:run
、call
、check_call
、check_output
subprocess.run(args[, stdout, stderr, shell ...])
- 执行args命令,返回值为CompletedProcess类;
- 若未指定stdout,则命令执行后的结果输出到屏幕上,函数返回值CompletedProcess中包含有args和returncode;
- 若指定有stdout,则命令执行后的结果输出到stdout中,函数返回值CompletedProcess中包含有args、returncode和stdout;
- 若执行成功,则returncode为0;若执行失败,则returncode为1;
- run函数返回值为CompletedProcess类,若需获取执行结果,可通过获取返回值的stdout和stderr来捕获;
若想获取args命令执行后的输出结果,命令为:output = subprocess.run(args, stdout=subprocess.PIPE).stdout
subprocess.call(args[, stdout, ...])
执行命令,并返回执行状态,其中shell参数为False时,命令需要通过列表的方式传入,当shell为True时,可直接传入命令
- 执行args命令,返回值为命令执行状态码;
- 若未指定stdout,则命令执行后的结果输出到屏幕;
- 若指定stdout,则命令执行后的结果输出到stdout;
- 若执行成功,则函数返回值为0;若执行失败,则函数返回值为1;
- (类似os.system)
subprocess.check_call(args[, stdout, ...])
- 执行args命令,返回值为命令执行状态码;
- 若未指定stdout,则命令执行后的结果输出到屏幕;
- 若指定stdout,则命令执行后的结果输出到stdout;
- 若执行成功,则函数返回值为0;若执行失败,抛出异常;
- 用法与subprocess.call()类似,区别是,当返回值不为0时,直接抛出异常
- (类似subprocess.run(args, check=True))
subprocess.check_output(args[, stderr, ...])
- 执行args命令,返回值为命令执行的输出结果;
- 若执行成功,则函数返回值为命令输出结果;若执行失败,则抛出异常;
- (类似
subprocess.run(args, check=True, stdout=subprocess.PIPE).stdout
) - 若需捕获错误信息,可通过stderr=subprocess.STDOUT来获取;
示例
1 |
|
3.2、subprocess 获取输出 getoutput
、getstatusoutput
subprocess.getoutput(cmd)
- 执行cmd命令,返回值为命令执行的输出结果(字符串类型);
- 执行失败,不会抛出异常(类似os.popen(cmd).read());
- cmd:参数,字符串类型;
subprocess.getstatusoutput(cmd)
- 执行cmd命令,返回值为元组类型(命令执行状态, 命令执行的输出结果);
- 元组中命令执行状态为0,表示执行成功;命令执行状态为1,表示执行失败;
- cmd:参数,字符串类型;
示例
1 |
|
4、subprocess.Popen类
subprocess.Popen类用于在一个新进程中执行一个子程序,上述subprocess函数均是基于subprocess.Popen类;
语法
subprocess.Popen(args[, bufsize, stdin, stdout, stderr, ...])
Popen类的构造函数,返回结果为subprocess.Popen对象;
参数
- args:需要执行的系统命令,可为字符串序列(列表或元组,shell为默认值False即可,建议为序列),也可为字符串(使用字符串时,需将shell赋值为True);
- shell:默认False,若args为序列时,shell=False;若args为字符串时,shell=True,表示通过shell执行命令;
- stdout、stdin、stderr:分别表示子程序标准输出、标准输入、标准错误,可为subprocess.PIPE、一个有效的文件描述符、文件对象或None。
若为subprocess.PIPE:代表打开通向标准流的管道,创建一个新的管道;
若为None:表示没有任何重定向,子进程会继承父进程;
stderr也可为subprocess.STDOUT:表示将子程序的标准错误输出重定向到了标准输出 - bufsize:默认0;指定缓冲策略,0表示不缓冲,1表示行缓冲,其它整数表示缓冲区大小,负数表示使用系统
- cwd:默认None;若非None,则表示将会在执行这个子进程之前改变当前工作目录;
- env:用于指定子进程的环境变量。若env为None,那么子进程的环境变量将从父进程中继承;若env非None,则表示子程序的环境变量由env值来设置,它的值必须是一个映射对象。
- universal_newlines: 不同系统的换行符不同。若True,则该文件对象的stdin,stdout和stderr将会以文本流方式打开;否则以二进制流方式打开
- preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用。
- Close_sfs:在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。我们不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout, stderr)。
- startupinfo与createionflags只在windows下有效,它们将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等。
subprocess.Popen对象常用方法
下列 PopenObject 为 subprocess.Popen 对象
- PopenObject.poll() :用于检查命令是否已经执行结束,若结束返回状态码;若未结束返回None;
- PopenObject.wait([timeout, endtime]):等待子进程结束,并返回状态码;若超过timeout(s)进程仍未结束,则抛出异常;
- PopenObject.send_signal(signal):发送信号signal给子进程;
- PopenObject.terminate():停止子进程;在windows平台下,该方法将调用Windows API TerminateProcess()来结束子进程。
- PopenObject.kill():杀死子进程;
- PopenObject.communicate([input, timeout]):与进程进行交互(如向stdin发送数据,或从stdout和stderr中读取数据),它会阻塞父进程,直到子进程完成;注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。
- input:表示将发送到子进程的字符串数据,默认为None;
- timeout:超时判断,若超过timeout秒后仍未结束则抛出TimeoutExpired异常;
- communicate返回值:一个元组(stdout_data, stderr_data)
- 要注意的是,subprocess.PIPE实际上为文本流提供一个缓存区。直到communicate()方法从PIPE中读取出PIPE中的文本.此方法比wait()好用。https://www.jianshu.com/p/8e582146bd4c
- Linux下此方法可能有BUG,慎用。
- PopenObject.pid:获取子进程的进程ID。
- PopenObject.returncode:获取进程的返回值。如果进程还没有结束,返回None。
subprocess.Popen 对象的文本或字节流控制
PopenObject.stdin:
若PopenObject中stdin为PIPE,则返回一个可写流对象;若encoding或errors参数被指定或universal_newlines参数为True,则此流是一个文件流,否则为字节流。
若PopenObject中stdin不是PIPE,则属性为None。
stdin输入流非None,可执行写操作即PopenObject.stdin.write(s)PopenObject.stdout:
若PopenObject中stdout为PIPE,则返回一个可读流对象;若encoding或errors参数被指定或universal_newlines参数为True,则此流是一个文件流,否则为字节流。
若PopenObject中stdout不是PIPE,则属性为None。
stdout输出流非None,可执行读操作即PopenObject.stdout.read()或.readlines()PopenObject.stderr:
若PopenObject中stderr为PIPE,则返回一个可读流对象;若encoding或errors参数被指定或universal_newlines参数为True,则此流是一个文件流,否则为字节流。
若PopenObject中stderr不是PIPE,则属性为None。
stderr错误流非None,可执行读操作即PopenObject.stderr.read()或.readlines()
1 |
|
报错解决
报错信息
1 |
|
解决
1 |
|