Python之路(第二十篇) subprocess模块
Posted Nicholas--Altshuler
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python之路(第二十篇) subprocess模块相关的知识,希望对你有一定的参考价值。
subprocess英文意思:子进程
那什么是进程呢?
(一)关于进程的相关理论基础知识
进程是对正在运行程序的一个抽象,进程的概念起源于操作系统,是操作系统最核心的概念,操作系统的其他所有内容都是围绕进程的概念展开的。
所以想要真正了解进程,必须事先了解操作系统.
程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作,这个繁琐的工作就是操作系统来干的,有了他,程序员就从这些繁琐的工作中解脱了出来,只需要考虑自己的应用软件的编写就可以了,应用软件直接使用操作系统提供的功能来间接使用硬件。
精简的说的话,操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。
操作系统所处的位置细说的话,操作系统应该分成两部分功能:
-
隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制(比如控制磁盘转动,移动磁头读写数据等细节).
-
将应用程序对硬件资源的竞态请求变得有序化例如:很多应用软件其实是共享一套计算机硬件,比方说有可能有三个应用程序同时需要申请打印机来输出内容,那么a程序竞争到了打印机资源就打印,然后可能是b竞争到打印机资源,也可能是c,这就导致了无序,打印机可能打印一段a的内容然后又去打印c...,操作系统的一个功能就是将这种无序变得有序。
进程是操作系统提供的最古老也是最重要的抽象概念之一。
什么是进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
简单的来说就是程序仅仅只是一堆代码而已,而进程指的是程序的运行过程,就是执行一个一系列程序代码的过程。
进程和程序的区别
-
-
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
-
程序是永久的,进程是暂时的。
(二)进程、父进程、子进程
父进程:如果进程a创建了进程b,那么进程a就是进程b的父进程,反之进程b就是进程a的子进程。
父进程定义在计算机领域,父进程(Parent Process)指已创建一个或多个子进程的进程。
父进程和子进程的关系是管理和被管理的关系,当父进程终止时,子进程也随之而终止。但子进程终止,父进程并不一定终止。
进程得到的是除了代码段是与父进程共享的意外,其他所有的都是得到父进程的一个副本,子进程的所有资源都继承父进程,得到父进程资源的副本,既然为副本,也就是说,二者并不共享地址空间。,两个是单独的进程,继承了以后二者就没有什么关联了,子进程单独运行。
(三)subprocess模块
任何操作系统上都可以通过命令行指令与操作系统进行交互,比如Linux平台下的shell、Windows下的cmd命令行。
Python来完成这些命令行指令的执行呢?另外,我们应该知道的是命令行指令的执行通常有两个我们比较关注的结果:
-
命令执行的状态码--表示命令执行是否成功
-
命令执行的输出结果--命令执行成功后的输出
早期的Python版本中,我们主要是通过os.system()、os.popen().read()等函数来执行命令行指令的,另外还有一个很少使用的commands模块。但是从Python 2.4开始官方文档中建议使用的是subprocess模块。
subprocess是Python 2.4中新增的一个模块,它允许你生成新的进程,连接到它们的 input/output/error 管道,并获取它们的返回(状态)码。这个模块的目的在于替换几个旧的模块和方法,如:
-
os.system
-
os.spawn*
1. subprocess模块中的常用函数
函数 | 描述 |
---|---|
subprocess.run() | Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。 |
subprocess.call() | 执行指定的命令,返回命令执行状态,其功能类似于os.system(cmd)。 |
subprocess.check_call() | Python 2.5中新增的函数。 执行指定的命令,如果执行成功则返回状态码,否则抛出异常。其功能等价于subprocess.run(..., check=True)。 |
subprocess.check_output() | Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常。 |
subprocess.getoutput(cmd) | 接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()和commands.getoutput(cmd)。 |
subprocess.getstatusoutput(cmd) | 执行cmd命令,返回一个元组(命令执行状态, 命令执行结果输出),其功能类似于commands.getstatusoutput()。 |
说明:
在Python 3.5之后的版本中,官方文档中提倡通过subprocess.run()函数替代其他函数来使用subproccess模块的功能;
在Python 3.5之前的版本中,我们可以通过subprocess.call(),subprocess.getoutput()等上面列出的其他函数来使用subprocess模块的功能;
subprocess.run()、subprocess.call()、subprocess.check_call()和subprocess.check_output()都是通过对subprocess.Popen的封装来实现的高级函数,因此如果我们需要更复杂功能时,可以通过subprocess.Popen来完成。
subprocess.getoutput()和subprocess.getstatusoutput()函数是来自Python 2.x的commands模块的两个遗留函数。它们隐式的调用系统shell,并且不保证其他函数所具有的安全性和异常处理的一致性。另外,它们从Python 3.3.4开始才支持Windows平台。
2. 上面各函数的定义及参数说明
函数参数列表:
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) subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None) subprocess.getstatusoutput(cmd) subprocess.getoutput(cmd)
参数说明:
-
args: 要执行的shell命令,默认应该是一个字符串序列,该参数用于启动进程。这可能是一个列表或一个字符串。如[\'df\', \'-Th\']或(\'df\', \'-Th\')【“\'df -Th”这里是linux命令,在终端里输入的,功能是显示磁盘分区相关信息】,也可以是一个字符串,如\'df -Th\',但是此时需要把shell参数的值置为True。
-
shell: 如果shell为True,那么指定的命令将通过shell执行。如果我们需要访问某些shell的特性,如管道、文件名通配符、环境变量扩展功能,这将是非常有用的。当然,python本身也提供了许多类似shell的特性的实现,如glob、fnmatch、os.walk()、os.path.expandvars()、os.expanduser()和shutil等。当shell=True时,表示在系统默认的shell环境中执行新的进程,此shell在windows表示为cmd.exe,在linux为/bin/sh。
shell=True
如果与不可信输入结合使用,传递可能会带来安全隐患。执行输入的命令最好不要设置为True,以防shell注入。 -
check: 如果check参数的值是True,且执行命令的进程以非0状态码退出,则会抛出一个CalledProcessError的异常,且该异常对象会包含 参数、退出状态码、以及stdout和stderr(如果它们有被捕获的话)。
-
参数stdin, stdout, stderr分别表示程序的标准输入、输出、错误句柄。他们可以是PIPE,文件描述符或文件对象,也可以设置为None,表示从父进程继承。PIPE表示创建管道。stderr特殊,可以设置成STDOUT,表示与标准输出一致
-
run()函数默认不会捕获命令执行结果的正常输出和错误输出,如果我们向获取这些内容需要传递subprocess.PIPE,然后可以通过返回的CompletedProcess类实例的stdout和stderr属性或捕获相应的内容;
-
call()和check_call()函数返回的是命令执行的状态码,而不是CompletedProcess类实例,所以对于它们而言,stdout和stderr不适合赋值为subprocess.PIPE;
-
check_output()函数默认就会返回命令执行结果,所以不用设置stdout的值,如果我们希望在结果中捕获错误信息,可以执行stderr=subprocess.STDOUT。
-
input: 该参数是传递给Popen.communicate(),通常该参数的值必须是一个字节序列,如果universal_newlines=True,则其值应该是一个字符串。
-
universal_newlines: 该参数影响的是输入与输出的数据格式,比如它的值默认为False,此时stdout和stderr的输出是字节序列;当该参数的值设置为True时,stdout和stderr的输出是字符串。
args 所有调用的必填参数,参数值为字符串、序列。处于方便,通常更偏向于提供序列。如果传递的是单一字符串,shell参数值必须为True,如果不提供其它任何参数,传递单一字符串的情况下,该字符串必须是需要执行的程序名。
subprocess.PIPE
subprocess.PIPE是特殊值,可用作标准输入,标准输出或标准错误参数,stdout 负责接收正常的输出,stderr 负责接收错误输出,subprocess.PIPE 负责处理整体的数据流,把错误信息通过管道赋值给变量stderr,把输出信息通过管道赋值给stdout.
3、subprocess.run()
subprocess.run()函数是Python3.5中新增的一个高级函数,功能是创建子进程执行某个命令,其返回值是一个subprocess.CompletedPorcess类的实例。
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False)
run()函数默认不会捕获命令执行结果的正常输出和错误输出,如果我们向获取这些内容需要传递subprocess.PIPE,然后可以通过返回的CompletedProcess类实例的stdout和stderr属性或捕获相应的内容。
subprocess.CompletedProcess类介绍
subprocess.CompletedProcess表示的是一个已结束进程的状态信息,它所包含的属性如下:
-
args: 用于加载该进程的参数,这可能是一个列表或一个字符串
-
returncode: 子进程的退出状态码。通常情况下,退出状态码为0则表示进程成功运行了;一个负值-N表示这个子进程被信号N终止了
-
stdout: 从子进程捕获的stdout。这通常是一个字节序列,如果run()函数被调用时指定universal_newlines=True,则该属性值是一个字符串。如果run()函数被调用时指定stderr=subprocess.STDOUT,那么stdout和stderr将会被整合到这一个属性中,且stderr将会为None
-
stderr: 从子进程捕获的stderr。它的值与stdout一样,是一个字节序列或一个字符串。如果stderr灭有被捕获的话,它的值就为None
-
check_returncode(): 如果returncode是一个非0值,则该方法会抛出一个CalledProcessError异常。
subprocess.run()例子
例子1(测试环境 Windows7 )
import subprocess v1 = subprocess.run(["ping","www.baidu.com"],stderr = subprocess.PIPE,stdout=subprocess.PIPE,universal_newlines=True) # 执行ping命令,niversal_newlines为True时,stdout将输出字符串 v2 = subprocess.run("calc",stderr = subprocess.PIPE,stdout=subprocess.PIPE) #打开系统自带的计算器 print(v1.stdout)
输出结果
正在 Ping www.a.shifen.com [119.75.213.61] 具有 32 字节的数据: 来自 119.75.213.61 的回复: 字节=32 时间=44ms TTL=52 来自 119.75.213.61 的回复: 字节=32 时间=43ms TTL=52 来自 119.75.213.61 的回复: 字节=32 时间=43ms TTL=52 来自 119.75.213.61 的回复: 字节=32 时间=44ms TTL=52 119.75.213.61 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 43ms,最长 = 44ms,平均 = 43ms
例子2(测试环境 ubuntu16.04.1)
import subprocess p = subprocess.run(["ls","-l"],stderr=subprocess.PIPE,stdout=subprocess.PIPE,universal_newlines=True) print(p.stdout)
输出结果
4、subprocess.call()
subprocess.call运行args描述的命令,等待命令完成后返回returncode属性。subprocess.call返回的是命令执行的状态码,而不是CompletedProcess类实例,所以对于它们而言,stdout和stderr不适合赋值subprocess.PIPE。
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
-
returncode: 子进程的退出状态码。通常情况下,退出状态码为0则表示进程成功运行了;一个负值-N表示这个子进程被信号N终止了。
subprocess.call()例子
例子1(测试环境 Windows7 )
import subprocess p = subprocess.call(["calc"]) print(p)
输出结果
0
例子2(测试环境 ubuntu16.04.1)
import subprocess p = subprocess.call("ls -l",shell=True) print("----------------------") print(p)
输出结果
总用量 16 drwxrwxr-x 3 nicholas nicholas 4096 5月 23 00:23 b -rw-rw-r-- 1 nicholas nicholas 0 5月 8 19:03 __init__.py -rwxrw-r-- 1 nicholas nicholas 16 5月 22 21:58 old_test.txt drwxrwxr-x 3 nicholas nicholas 4096 5月 23 00:23 temp -rw-rw-r-- 1 nicholas nicholas 100 5月 30 20:02 test.py ---------------------- 0
5、subprocess.Popen介绍
subprocess.Popen类用于在一个新的进程中执行一个子程序,用来创建子进程。
上面的subprocess.run()、subprocess.call()等介绍的这些函数都是基于subprocess.Popen类实现的,通过使用这些被封装后的高级函数可以很方面的完成一些常见的需求。由于subprocess模块底层的进程创建和管理是由Popen类来处理的。
(1)subprocess.Popen的构造函数
class 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, startup_info=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=())
参数说明:
-
args: 要执行的shell命令,可以是字符串,也可以是命令各个参数组成的序列。当该参数的值是一个字符串时,该命令的解释过程是与平台相关的,因此通常建议将args参数作为一个序列传递。
-
bufsize: 指定缓存策略,0表示不缓冲,1表示行缓冲,其他大于1的数字表示缓冲区大小,负数 表示使用系统默认缓冲策略。
-
stdin, stdout, stderr: 分别表示程序标准输入、输出、错误句柄。合法值为PIPE,DEVNULL,已存在文件描述符(一个正整数),已存在文件对象和None。 PIPE表示应该创建通往子进程的管道。DEVNULL表示应该使用指定文件os.devnull。默认参数None则表示无进行重定向,子进程文件句柄从父进程继承。此外,stderr还可以是STDOUT,表明子进程的错误数据应该被放进相同的文件句柄stdout。
-
preexec_fn: 用于指定一个将在子进程运行之前被调用的可执行对象,只在Unix平台下有效。
-
close_fds: 如果该参数的值为True,则除了0,1和2之外的所有文件描述符都将会在子进程执行之前被关闭。
默认值根据平台而异。Unix平台总是默认为True。在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。(标准的输入,输出和错误输出分别表示为STDIN,STDOUT,STDERR,也可以用0,1,2来表示。)
-
shell: 该参数用于标识是否使用shell作为要执行的程序,如果shell值为True,则建议将args参数作为一个字符串传递而不要作为一个序列传递。为True时,表示将通过shell来执行。
-
cwd: 如果该参数值不是None,则该函数将会在执行这个子进程之前改变当前工作目录到cwd。
-
env: 用于指定子进程的环境变量,如果env=None,那么子进程的环境变量将从父进程中继承。如果env!=None,它的值必须是一个映射对象。
-
universal_newlines: 如果该参数值为True,则该文件对象的stdin,stdout和stderr将会作为文本流被打开,否则他们将会被作为二进制流被打开。
-
startupinfo和creationflags: 这两个参数只在Windows下有效,它们将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如主窗口的外观,进程优先级等。
(2) subprocess.Popen类的实例可调用的方法
方法 | 描述 |
---|---|
Popen.poll() | 用于检查子进程(命令)是否已经执行结束,没结束返回None,结束后返回状态码。 |
Popen.wait(timeout=None) | 等待子进程结束,并返回状态码;如果在timeout指定的秒数之后进程还没有结束,将会抛出一个TimeoutExpired异常。 |
Popen.communicate(input=None, timeout=None) | 该方法可用来与进程进行交互,比如发送数据到stdin,从stdout和stderr读取数据,直到到达文件末尾。 |
Popen.send_signal(signal) | 发送指定的信号给这个子进程。 |
Popen.terminate() | 停止该子进程。 |
Popen.kill() | 杀死该子进程。 |
关于communicate()方法的说明:
-
该方法中的可选参数 input 应该是将被发送给子进程的数据,或者如没有数据发送给子进程,该参数应该是None。input参数的数据类型必须是字节串,如果universal_newlines参数值为True,则input参数的数据类型必须是字符串。
-
该方法返回一个元组(stdout_data, stderr_data),这些数据将会是字节穿或字符串(如果universal_newlines的值为True)。
-
如果在timeout指定的秒数后该进程还没有结束,将会抛出一个TimeoutExpired异常。捕获这个异常,然后重新尝试通信不会丢失任何输出的数据。但是超时之后子进程并没有被杀死,为了合理的清除相应的内容,一个好的应用应该手动杀死这个子进程来结束通信。
-
需要注意的是,这里读取的数据是缓冲在内存中的,所以,如果数据大小非常大或者是无限的,就不应该使用这个方法。
例子1(测试环境 ubuntu16.04.1)
import subprocess # 创建子进程,执行“ls -l”,查看当前目录的文件具体信息 p = subprocess.Popen("ls -l",stdout=subprocess.PIPE,shell=True,universal_newlines=True) #p是Popen的一个实例对象 print(p.stdout.read()) #调用Popen的stdout,read()方法读取输出结果 # 修改执行的目录到/temp,然后再创建子进程,执行“ls -l”,查看当前目录文件的具体信息 p1 = subprocess.Popen("ls -l",stdout=subprocess.PIPE,shell=True,universal_newlines=True,cwd=\'/tmp\') print(p1.stdout.read())
输出结果
例子2(测试环境 ubuntu16.04.1)
import subprocess p = subprocess.Popen(["python3"],shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) #实例化suprocess.Popen类创建子进程p对象 p.stdin.write("print(\'nicholas 1\') \\n") p.stdin.write("print(\'hello 2\') \\n") p.stdin.write("print(\'hworld 3\') \\n") out,err = p.communicate() print(out)
输出结果
nicholas 1 hello 2 hworld 3
分析执行过程
1、实例化suprocess.Popen类创建子进程P对象,调用shell终端,在终端里执行"python3"命令,终端进入python环境,
2、向子进程输入"print(\'nicholas 1\') \\n"语句,向子进程输入"print(\'hello 2\') \\n"语句,向子进程输入"print(\'hworld 3\') \\n"语句
3、通过调用对象p的communicate()方法,获取子进程的输出、错误
4、打印子进程的输出
5、注意参数universal_newlines是True时,stdin.write要传入字符串,参数是False时要传入bytes类型。
例子3(测试环境 Windows7 )
import subprocess p = subprocess.Popen(["python"],shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE, universal_newlines=True) out,err = p.communicate(input="print(\'nicholas niubi\')") print(out)
输出结果
nicholas niubi
分析:
1、实例化suprocess.Popen类创建子进程p对象,打开Windows命令行,执行“python”命令,进入python环境
2、通过调用对象p的communicate()方法向stdin发送数据"print(\'nicholas niubi\')",并分别获取对象p的输出和错误
3、打印输出
4、注意参数universal_newlines是True时,input要传入字符串,参数是False时要传入bytes类型。
参考资料
[1]https://www.cnblogs.com/Eva-J/articles/8253521.html
[2]https://www.cnblogs.com/Eva-J/articles/8253549.html
[3]https://www.cnblogs.com/yyds/p/7288916.html
以上是关于Python之路(第二十篇) subprocess模块的主要内容,如果未能解决你的问题,请参考以下文章