subprocess介绍
需要用到Python来执行shell脚本, 因此需要查看下subprocess模块文档。
根据官网文档描述:subprocess模块用于创建子进程, 这个模块用于替换旧版本中的一些模块, 如:os.system,
os.spawn*, os.popen*, os.popen*, popen2.*, commands.*, subprocess允许你能创建很多子进程, 创建的时候能能指定子进程和子进程的输入、输出、错误输出管道, 执行后能获取输出结果和执行状态。
subprocess模块的常用方法用法介绍
subprocess.run() --> python3.5中新增的函数, 执行指定的命令, 等待命令执行完成后返回一个包含执行结果的 CompletedProcess类的实例。
subprocess.call(): --> 执行指定的命令, 返回命令执行状态, 功能类似羽os.system(cmd)
subprocess.check_call(): --> python2.5中新增的函数, 执行指定的命令, 如果执行成功则返回状态码, 否则抛出异常。
【Tips】: 在python3.5之后的版本中, 官方文档中提倡通过subprocess.run()函数替代其他函数来使用subprocess模块的功能。在python3.5之前的版本中, 我们可以通过subprocess.call()来使用subprocess模块的功能。subprocess.run(), subprocess.call(), subprocess.check_call()都是通过对subprocess.Popen的封装来实现的高级函数。
###
三. 这几个函数的定义以及参数
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, universal_newlines=False)
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
参数说明:
args: 要执行的shell命令, 默认应该是一个字符串序列, 如['ls', '-l'], 也可以是一个字符串如: 'ls -l', 但是此时需要把shell参数的值置为True。
【Tips】--> shell=True参数会让subprocess.call接受字符串类型的变量作为命令, 并调用shell去执行这个字符串, 当shell=False时, subprocess.call只接受数组变量作为命令, 并将数组的第一个元素作为命令, 剩下的全部作为该命令的参数。官方不推荐使用shell=True。
p = subprocess.run(["pwd"])
# p1 = subprocess.run(["cd"])
# p2 = subprocess.run(["ls", "-l"])
print(p)
###
###
subprocess.Popen
subprocess的目的就是启动一个新的进程并且与之通信。
subprocess模块中只定义了一个类: Popen。可以使用Popen来创建进程,并与进程进行复杂的交互。它的构造函数如下:
class subprocess.Popen( args,
bufsize=0,
executable=None,
stdin=None,
stdout=None,
stderr=None,
preexec_fn=None,
close_fds=False,
shell=False,
cwd=None,
env=None,
universal_newlines=False,
startupinfo=None,
creationflags=0)
###
参数args
参数args可以是字符串或者序列类型(如:list,元组),用于指定进程的可执行文件及其参数。如果是序列类型,第一个元素通常是可执行文件的路径。我们也可以显式的使用executeable参数来指定可执行文件的路径。
subprocess.Popen(["cat","test.txt"])
subprocess.Popen("cat test.txt")
这两个之中,后者将不会工作。因为如果是一个字符串的话,必须是程序的路径才可以。(考虑unix的api函数exec,接受的是字符串
列表)
但是下面的可以工作
subprocess.Popen("cat test.txt", shell=True)
这是因为它相当于
subprocess.Popen(["/bin/sh", "-c", "cat test.txt"])
在*nix下,当shell=False(默认)时,Popen使用os.execvp()来执行子程序。args一般要是一个【列表】。如果args是个字符串的
话,会被当做是可执行文件的路径,这样就不能传入任何参数了。
注意:
shlex.split()可以被用于序列化复杂的命令参数,比如:
>>> shlex.split('ls ps top grep pkill')
['ls', 'ps', 'top', 'grep', 'pkill']
>>>import shlex, subprocess
>>>command_line = raw_input()
/bin/cat -input test.txt -output "diege.txt" -cmd "echo '$MONEY'"
>>>args = shlex.split(command_line)
>>> print args
['/bin/cat', '-input', 'test.txt', '-output', 'diege.txt', '-cmd', "echo '$MONEY'"]
>>>p=subprocess.Popen(args)
###
可以看到,空格分隔的选项(如-input)和参数(如test.txt)会被分割为列表里独立的项,但引号里的或者转义过的空格不在此列
。这也有点像大多数shell的行为。
在*nix下,当shell=True时,如果arg是个字符串,就使用shell来解释执行这个字符串。如果args是个列表,则第一项被视为命令,
其余的都视为是给shell本身的参数。也就是说,等效于:
subprocess.Popen(['/bin/sh', '-c', args[0], args[1], ...])
####
参数bufsize
参数bufsize一般0 无缓冲,1 行缓冲,其他正值 缓冲区大小,负值 采用默认系统缓冲(一般是全缓冲)
###
参数executable
executable一般不用,args字符串或列表第一项表示程序名
###
参数stdin, stdout, stderr
参数stdin, stdout, stderr分别表示程序的标准输入、输出、错误句柄。他们可以是PIPE,文件描述符或文件对象,也可以设置为None,表示从父进程继承。
subprocess.PIPE
在创建Popen对象时,subprocess.PIPE可以初始化stdin, stdout或stderr参数。表示与子进程通信的标准流。
subprocess.STDOUT
创建Popen对象时,用于初始化stderr参数,表示将错误通过标准输出流输出。
###
参数shell
如果参数shell设为true,程序将通过shell来执行。
####
参数env
参数env是字典类型,用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
###
Popen的方法:
Popen.poll()
用于检查子进程是否已经结束。设置并返回returncode属性。
Popen.wait()
等待子进程结束。设置并返回returncode属性。
Popen.communicate(input=None)
与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。Communicate()返回一个元组:(stdoutdata, stderrdata)。注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。
Popen.send_signal(signal)
向子进程发送信号。
Popen.terminate()
停止(stop)子进程。在windows平台下,该方法将调用Windows API TerminateProcess()来结束子进程。
Popen.kill()
杀死子进程。
Popen.pid
获取子进程的进程ID。
Popen.returncode
获取进程的返回值。如果进程还没有结束,返回None。
###
进程通信:
如果想得到进程的输出,管道是个很方便的方法:
p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutput,erroutput) = p.communicate()
###
import subprocess
p = subprocess.Popen("ls", shell=True, stdout=subprocess.PIPE)
(stdoutput, erroutput) = p.communicate()
print(stdoutput)
print(erroutput)
####
p.communicate会一直等到进程退出,并将标准输出和标准错误输出返回,这样就可以得到子进程的输出了。
上面的例子通过communicate给stdin发送数据,然后使用一个tuple接收命令的执行结果。
上面,标准输出和标准错误输出是分开的,也可以合并起来,只需要将stderr参数设置为subprocess.STDOUT就可以了,这样子:
p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(stdoutput,erroutput) = p.communicate()
如果你想一行行处理子进程的输出,也没有问题:
p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
buff = p.stdout.readline()
if buff == '' and p.poll() != None:
break
死锁
但是如果你使用了管道,而又不去处理管道的输出,那么小心点,如果子进程输出数据过多,死锁就会发生了,比如下面的用法:
p=subprocess.Popen("longprint", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
p.wait()
longprint是一个假想的有大量输出的进程,当输出达到4096时,死锁就发生了。当然,如果我们用p.stdout.readline或者p.communicate去清理输出,那么无论输出多少,死锁都是不会发生的。或者我们不使用管道,比如不做重定向,或者重定向到文件,也都是可以避免死锁的。
subprocess还可以连接起来多个命令来执行。
在shell中我们知道,想要连接多个命令可以使用管道。
在subprocess中,可以使用上一个命令执行的输出结果作为下一次执行的输入。例子如下:
####
#####
技术改变命运