Python 标准库之 subprocesss

Python 目前已经废弃了 os.system、os.spawn*、os.popen*、popen2.*、commands.* 来执行其他语言的命令,取而代之的是 subprocess 模块。

运行 Python 的时候,我们都是在创建并运行一个进程。像 Linux 进程那样,一个进程可以 fork 一个子进程,并让这个子进程 exec 另外一个程序。在 Python 中,我们通过标准库中的 subprocess 包来 fork 一个子进程,并运行一个外部的程序。

subprocess 允许你能创建很多子进程,创建的时候能指定子进程和子进程的 input/output/error 管道,执行后能获取输出结果和执行状态。

1. 常用的方法有

  • subprocess.call
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

运行 args 描述的命令,等待命令完成后返回状态码,如果超过 timeout,子进程将会被 kill 掉,并再次等待。子进程被终止后会抛出 TimeoutExpired 异常。该方法必须等待命令执行完毕,会阻塞主进程。

注意:针对该函数,不要使用 stdout = PIPE 或 stderr = PIPE。因为不是从当前进程中读取管道(pipe),如果子进程没有生成足够的输出来填充 os 的管道缓冲区,可能会阻塞子进程。其中 shell 参数为 False 时,命令需要通过列表的方式传入,当 shell 为 True 时,可直接传入命令 。

 In [54]: subprocess.call("df -h", shell=True)
   Filesystem      Size  Used Avail Use% Mounted on
   udev            7.8G     0  7.8G   0% /dev
   tmpfs           1.6G   18M  1.6G   2% /run
   /dev/sda3       909G  8.7G  855G   2% /
   tmpfs           7.8G   98M  7.7G   2% /dev/shm
   tmpfs           5.0M  4.0K  5.0M   1% /run/lock
   tmpfs           7.8G     0  7.8G   0% /sys/fs/cgroup
   /dev/sda2       512M  3.5M  509M   1% /boot/efi
   tmpfs           1.6G   64K  1.6G   1% /run/user/1000
Out[54]: 0

In [56]: subprocess.call(['df','-h'], shell=False)
	Filesystem      Size  Used Avail Use% Mounted on
	udev            7.8G     0  7.8G   0% /dev
	tmpfs           1.6G   18M  1.6G   2% /run
	/dev/sda3       909G  8.7G  855G   2% /
	tmpfs           7.8G   98M  7.7G   2% /dev/shm
	tmpfs           5.0M  4.0K  5.0M   1% /run/lock
	tmpfs           7.8G     0  7.8G   0% /sys/fs/cgroup
	/dev/sda2       512M  3.5M  509M   1% /boot/efi
	tmpfs           1.6G   64K  1.6G   1% /run/user/1000
Out[56]: 0

  • subprocess.check_call
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

用法与 subprocess.call() 类似,如果执行状态码是 0 ,则返回0,否则抛异常。同样会阻塞主进程。

  • subprocess.check_output
subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)

用法与上面两个方法类似,区别是,如果当返回值为0时,直接返回输出结果,如果返回值不为0,直接抛出异常。同样会阻塞主进程。

  • subprocess.Popen

在一些复杂场景中,我们需要将一个进程的执行输出作为另一个进程的输入。在另一些场景中,我们需要先进入到某个输入环境,然后再执行一系列的指令等。

subprocess.Popen(args, bufsize=0, stdin=None, stdout=None, stderr=None,
                 shell=False, cwd=None,env=None,universal_newlines=False)
   Popen 对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的 wait() 方法,父进程才会等待 (也就是阻塞block)。

   该方法有以下参数:

   args:必填参数shell命令,可以是字符串,或者序列类型,如 list, tuple

   bufsize:缓冲区大小,可不用关心

   stdin,stdout,stderr:分别表示程序的标准输入,标准输出及标准错误

   shell:与上面方法中用法相同,如果shell为True,指定命令将通过shell执行。出于安全考虑,如果命令字符串参数需要通过外部的输入来构成的时候,强烈建议设置shell = False,不然容易造成shell注入之类的。

   cwd:用于设置子进程的当前目录

   env:用于指定子进程的环境变量。如果env=None,则默认从父进程继承环境变量

   universal_newlines:不同系统的的换行符不同,当该参数设定为true时,则表示使用\n作为换行符(Unix行结束符:'\n', Windows行结束符:'\r\n')

示例:

# 在/root下创建一个suprocesstest的目录
import subprocess
a = subprocess.Popen('mkdir subprocesstest',shell=True,cwd='/root')

# 将一个子进程的输出,作为另一个子进程的输入
child1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["grep","0:0"],stdin=child1.stdout, stdout=subprocess.PIPE)
out = child2.communicate()

Popen 类实例有以下几个方法:

child = subprocess.Popen('sleep 60',shell=True,stdout=subprocess.PIPE)

注意:

   Popen()             		# 创建子进程并执行,该子进程执行并不会阻塞父进程,该子进程和当前父进程是异步执行的。
   child.poll()    			# 检查子进程状态,是否中断,设置并返回returncode
   child.kill()     		# 终止子进程
   child.send_signal()    	# 向子进程发送信号
   child.terminate()   		# 终止子进程
   child.wait()    			# 等待子进程终止,设置并返回returncode。如果进程在timeout(单位秒)之后依然
   #没终止,则抛出TimeoutExpired异常,可以捕获该异常并再次尝试等待。wait 函数会阻塞当前父进程,等待子进程运行结束,子进程结束之后返回子进程的退出值,0表示正常执行完成。

警告

当使用stdout = PIPE and / or stderr = PIPE时,如果子进程生成足够的输出到管道,这会阻止操作系统管道缓冲区接收更多数据,进而造成死锁。
为了避免该事件,使用 communicate()

   child.communicate(input=None, timeout=None)

input:可选参数,参数值为发送给子进程的数据,如果不需要发送数据,则为None。如果universal_newlines为 False,则input数据类型必须为字节,否则可为字符串。

communicate() 返回一个元组:(stdoutdata, stderrdata),会阻塞当前父进程,直到子进程执行完成,父进程才会往下继续执行。与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。

注意:

如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。

如果进程在timeout(单位秒)之后依然没终止,则抛出 TimeoutExpired 异常。子进程不会被 kill 掉,所以为了完成交互,恰当的清理友好执行的程序,应该 kill 子进程。

with subprocess.Popen(['dir'], stdout=subprocess.PIPE,stderr=subprocess.PIPE, shell=True, universal_newlines = True) as proc:
try:
	outs, errs = proc.communicate(timeout=15) #超时时间为15秒
	print(outs, errs)
except subprocess.TimeoutExpired:
	proc.kill()
	outs, errs = proc.communicate()
	print(outs, errs)

注意:读取的数据缓存在内存,所以如果数据太大或者无限制,不要使用该函数。

  • 其它方法

subprocess.PIPE 可用于Popen函数stdin,stdout或者stderr参数的指特定值,表示必须打开一个指向标准流的管道。

subprocess.STDOUT 可用于Popen函数stdin,stdout或者stderr参数的指特定值,表示标准错误信息必须一起写入同样的句柄,比如标准输出。

参考:

http://blog.sina.com.cn/s/blog_13cc013b50102wv83.html

https://blog.csdn.net/songfreeman/article/details/50735045

https://www.cnblogs.com/sunailong/p/5162748.html

https://blog.csdn.net/seetheworld518/article/details/48805261

https://hacpai.com/article/1462524113048

https://blog.csdn.net/ronnyjiang/article/details/53333538

你可能感兴趣的:(Python)