如需转载请注明出处。
win10 64位、Python 3.6.3、Sublime Text 3。
一、首先明白一些概念:
1、操作系统(OS,operating system)。
是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件。
OS的组成部分有:内核、驱动程序、接口库、外围。
常见的OS:Windows、Linux、iOS、Android。
2、命令行(Command Line)
在任何一个OS上,都可以通过命令提示符与OS进行交互(如文件目录的操作、文件搜索、文件系统、网络等等):如
Windows:命令行(Command Processor,CMD),即cmd.exe这个命令行程序;
Linux:shell命令(内置)和Linux命令。
3、线程(threading)、进程(process)。
线程是OS能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任何。
在Linux 进程中,一个进程可fork
一个子进程,并让该子进程exec
另外一个程序。(Windows中没有fork一说)
fork,译作“分叉”。在计算机学科,它是一个关于进程管理的术语,本质是新开一个进程(进程就“分叉”了,形象!),但不是从磁盘加载代码,而是从内存现有进程复制一份。在子进程运行后,虽然它继承了父进程的一切数据,但实际上数据已分开了,相互之间不影响。
此举用于节省系统在空间上的开销。
参考文档:
Python浅析线程和进程
综上,明白了 OS、命令行、进程这3个概念。命令行指令的执行有两个关注的结果:
①命令执行的状态码:表示命令执行是否成功;
②命令执行的输出结果:命令执行成功后的输出。
二、在Python的世界里,如何完成命令行指令的执行???
在Python 2.3以前,是通过os.system()
、os.popen().read()
函数和commands
模块(注:commands
模块只存在于Python 2.7,且不支持Windows平台。而且它实质上是对os.popen()
的封装)来执行命令行指令。在Python 2.4中,新增了subprocess
模块。
函数名 | 说明 |
---|---|
os.system(command) |
返回命令执行状态码,并将命令执行结果输出到屏幕 |
os.popen(command).read() |
可以获取命令执行结果,但无法获取命令执行状态码。os.popen(command) 函数得到的是一个文件对象,所以支持read() 和write() ,具体根据command而定。 |
commands.getstatusoutput(command) |
返回一个元组(命令执行状态码、执行结果) |
subprocess 模块 |
新开一个子进程,让其执行别的程序 |
(venv) D:\ATP>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.system('dir')
驱动器 D 中的卷是 软件
卷的序列号是 000A-190C
D:\ATP 的目录
2018/10/16 21:04 <DIR> .
2018/10/16 21:04 <DIR> ..
2018/10/16 21:02 <DIR> apk
2018/10/16 21:01 <DIR> common
2018/10/16 20:57 3 config.txt
2018/10/16 20:59 <DIR> LOGS
2018/10/16 20:57 <DIR> pref
2018/10/16 20:58 3 README.txt
2018/10/16 20:56 <DIR> regreUI
2018/10/16 21:03 3 run.py
2018/10/16 20:15 <DIR> UI
2018/10/12 22:36 <DIR> venv
3 个文件 9 字节
9 个目录 142,012,526,592 可用字节
0
>>> os.system("adb devices")
List of devices attached
10.2.0.140:5555 device
0
>>> ret = os.popen("dir").read()
>>> print(ret)
驱动器 D 中的卷是 软件
卷的序列号是 000A-190C
D:\ATP 的目录
2018/10/16 21:04 <DIR> .
2018/10/16 21:04 <DIR> ..
2018/10/16 21:02 <DIR> apk
2018/10/16 21:01 <DIR> common
2018/10/16 20:57 3 config.txt
2018/10/16 20:59 <DIR> LOGS
2018/10/16 20:57 <DIR> pref
2018/10/16 20:58 3 README.txt
2018/10/16 20:56 <DIR> regreUI
2018/10/16 21:03 3 run.py
2018/10/16 20:15 <DIR> UI
2018/10/12 22:36 <DIR> venv
3 个文件 9 字节
9 个目录 142,007,431,168 可用字节
>>> ouput = os.popen("adb devices").read()
>>> print(ouput)
List of devices attached
10.2.0.140:5555 device
上述仅示例os.system()
、os.popen().read()
。
三、重点:subprocess模块
subprocess,译作“子进程”。在运行Python程序时,都是在创建并运行一个进程。在Python中,可通过标准库的subprocess
模块 来fork一个子进程,并运行一个外部程序(如执行一条cmd命令)。
subprocess
模块是Python 2.4新增的一个模块,它允许生成新的进程(子进程),连接到它们的input、output、error管道,并获取它们的返回(状态)码。subprocess
模块的目的:替换几个旧的模块和方法(如os.system()
、os.spawn*()
,甚至os.popen
,当然commands
更是舍弃了)。
subprocess
模块中定义了数个创建子进程的函数,这些函数分别以不同的方式创建子进程,可根据需要进行选取。subprocess还提供了管理标准流(standard stream)和管道(pipe)的工具,从而在进程之间使用文本通信。
1、subprocess
模块中常用函数:
函数名 | 说明 |
---|---|
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, env=None) |
Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompleteProcess 类的实例。Python 3.6新增加encoding 和errors 参数。 |
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None) |
执行指定命令,返回命令执行状态。其功能类似于os.system(cmd) 。 |
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None) |
Python 2.5中新增的函数。执行指定命令,如果执行成功则返回状态码 0 ,否则抛出subprocess.CalledProcessError异常,该对象包含returncode属性,可用try…except语句块进行检查。其功能等价于subprocess.run(..., check=True) 。 |
subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, universal_newlines=False, timeout=None) |
Python 2.7中新增的函数。执行指定的命令,如果执行状态码为0 ,则返回命令执行结果,否则抛出subprocess.CalledProcessError异常,该属性包含returncode属性、output属性,ouput属性为标准输出的输出结果,可用try…except语句块进行检查。 |
subprocess.getoutput(cmd) |
接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read() 和commands.getoutput(cmd) 。 |
subprocess.getstatusoutput(cmd) |
执行cmd命令,返回一个元组(命令执行状态、命令执行结果输出),其功能类似于commands.getstatusoutput() 。 |
上述函数均是 父进程等待子进程完成,然后返回对应的结果。
PS:
①、在Python 3.5之后的版本,在使用subprocess
模块的功能时,官方文档提倡使用subprocess.run()
函数 来替代其他函数;这也就是官方文档中提到的Older high-level API。
②、在Python 3.5之前的版本,在使用subprocess
模块的功能时,可使用上述如subprocess.call()
、subprocess.getoutput()
等函数。
③、subprocess.run()
、subprocess.call()
、subprocess.check_call()
、subprocess.check_output()
都是通过对subprocess.Popen类
的封装来实现的高级函数。因此,若要实现更复杂的功能,可通过subprocess.Popen类
来完成。
④、subprocess.getoutput()
、subprocess.getstatusoutput()
函数均是来自Python 2.7的commands
模块的两个遗留函数。它们隐式地调用系统shell,并且不保证其他函数所具有的安全性和异常处理的一致性。而且,两者从Python 3.3.4才开始支持Windows平台。
上述函数中常见参数说明:
参数 | 说明 |
---|---|
args |
要执行的shell命令(可理解为 args指定的一个外部程序),默认是一个字符串序列,如['df', '-Th'] 或('df', '-Th') ,也可是一个字符串,如'df -Th' ,但此时要把shell参数的值为True 。 |
shell |
如果shell 为True ,那么指定的命令将通过shell执行。如果我们需要访问某些shell的特性,如管道、文件名通配符、环境变量扩展功能,这将是非常有用的。当然,Python本身也提供了许多类似shell的特性的实现,如glob 、fnmatch 、os.walk() 、os.path.expandvars() 、os.expanduser() 和shutil 等。 |
check |
如果check 参数的值是True ,且执行命令的进程以非0 状态码退出,则会抛出一个CalledProcessError 的异常,且该异常对象会包含参数、退出状态码、以及stdout 和stderr (如果它们有被捕获的话)。 |
stdout ,stderr |
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的输出是字符串。 |
示例:以subprocess.run()
为主。
(venv) D:\ATP>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> subprocess.run(["adb", "devices"])
List of devices attached
CompletedProcess(args=['adb', 'devices'], returncode=0)
>>> subprocess.run(["adb", "connect", "10.2.0.140"])
connected to 10.2.0.140:5555
CompletedProcess(args=['adb', 'connect', '10.2.0.140'], returncode=0)
>>> subprocess.run("adb connect 10.2.0.140")
already connected to 10.2.0.140:5555
CompletedProcess(args='adb connect 10.2.0.140', returncode=0)
>>> subprocess.run(["ping", "www.google.com.hk"])
正在 Ping www-wide.l.google.com [74.125.204.199] 具有 32 字节的数据:
来自 74.125.204.199 的回复: 字节=32 时间=92ms TTL=41
来自 74.125.204.199 的回复: 字节=32 时间=92ms TTL=41
来自 74.125.204.199 的回复: 字节=32 时间=92ms TTL=41
来自 74.125.204.199 的回复: 字节=32 时间=92ms TTL=41
74.125.204.199 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 92ms,最长 = 92ms,平均 = 92ms
CompletedProcess(args=['ping', 'www.google.com.hk'], returncode=0)
根据上述打印可知:官方更推荐使用args
,即将命令写成多个单词组成的列表形式。
2、subprocess
模块中常用类:
2.1 subprocess.CompletedProcess
类
subprocess.run()
函数是Python 3.5新增的一个高级函数,其返回值是一个subprocess.CompletedProcess
类的实例。因此,subprocess.CompletedProcess
类也是Python 3.5才存在的。它表示 一个已结束(已完成的)进程的状态信息,所包含的属性如下:
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
异常。
2.2 重点:subprocess.Popen
类
subprocess.Popen类
用于创建一个新的进程,让其执行一个子程序(另外的程序),并与它进行通信,获取标准输入、输出、错误以及返回码。该类生成的对象 用来代表子进程。
上述讲述的subprocess.run()
、subprocess.call()
、subprocess.check_call()
、subprocess.check_output()
这些函数均是基于subprocess.Popen类
实现的,通过使用这些被封装后的高级函数可方便地完成一些常见需求。
由于subprocess
模块底层的进程创建和管理是由Popen类
来处理的,因此,当无法通过这些高级函数来实现一些不太常见的功能(如个性化需求)时,就可以通过subprocess.Popen类
提供的灵活api来完成。
与上述基于subprocess.Popen类封装而来的标准库中的函数不同,Popen类的对象创建之后,主程序不会自动等待子进程完成。我们必须调用wait()
方法,父进程才会等待(即阻塞 block)。
①、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, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, encoding=None, errors=None)
参数说明:
args
: 要执行的shell命令,可以是字符串,也可以是命令各个参数组成的序列。当该参数的值是一个字符串时,该命令的解释过程是与平台相关的,因此通常建议将args参数作为一个序列传递。bufsize
: 指定缓存策略,0
表示不缓冲,1
表示行缓冲,其他大于1
的数字表示缓冲区大小,负数
表示使用系统默认缓冲策略。stdin
,stdout
,stderr
: 分别表示程序标准输入、输出、错误句柄。preexec_fn
: 用于指定一个将在子进程运行之前被调用的可执行对象,只在Unix平台下有效。close_fds
: 如果该参数的值为True
,则除了0
,1
和2
之外的所有文件描述符都将会在子进程执行之前被关闭。shell
: 该参数用于标识是否使用shell作为要执行的程序,如果shell
值为True
,则建议将args
参数作为一个字符串传递而不要作为一个序列传递。cwd
: 如果该参数值不是None
,则该函数将会在执行这个子进程之前改变当前工作目录。env
: 用于指定子进程的环境变量,如果env=None
,那么子进程的环境变量将从父进程中继承。如果env!=None
,它的值必须是一个映射对象。universal_newline
s: 如果该参数值为True
,则该文件对象的stdin
,stdout
和stderr
将会作为文本流被打开,否则他们将会被作为二进制流被打开。startupinfo
和creationflags
: 这两个参数只在Windows下有效,它们将被传递给底层的CreateProcess()
函数,用于设置子进程的一些属性,如主窗口的外观,进程优先级等。
②、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
异常。捕获这个异常,然后重新尝试通信不会丢失任何输出的数据。但是超时之后子进程并没有被杀死,为了合理的清除相应的内容,一个好的应用应该手动杀死这个子进程来结束通信。 - 需要注意的是,这里读取的数据是缓冲在内存中的,所以,如果数据大小非常大或者是无限的,就不应该使用这个方法。
③、subprocess.Popen类
的实例(对象)拥有的属性:
属性名 | 说明 |
---|---|
Popen.pid |
获取子进程的进程ID |
Popen.returncode |
获取进程的返回码。如果进程未结束,则返回None |
Popen.args |
获取传递给Popen的args参数。它是一个字符串或一个参数序列 |
Popen.stdin |
获取stdin参数值。 |
Popen.stdout |
获取stdout参数值。 |
Popen.stderr |
获取stderr参数值。 |
2.3 其他:常量、异常
3个常量:
常量名 | 说明 |
---|---|
subprocess.DEVNULL |
是一个特殊值,可用于Popen 对象的stdin 、stdout 、stderr 参数,并指示将使用特殊文件os.devnull 。Python 3.3中新增的。 |
subprocess.PIPE |
在创建Popen 对象时,subprocess.PIPE 可以初始化为stdin , stdout 或stderr 的参数,表示与子进程通信的标准输入流,标准输出流以及标准错误。 |
subprocess.STDOUT |
作为Popen 对象的stderr 的参数,表示将标准错误通过标准输出流输出。 |
3个异常类 excepiton:
类名 | 说明 |
---|---|
subprocess.SubprocessError |
Python 3.3新增的。它是subprocess 模块中所有其他异常类的基类。 |
subprocess.TimeoutExpired |
Python 3.3新增的。是SuprocessError 的子类。当等待子进程超时时引发。 |
subprocess.CalledProcessError |
Python 3.3新增的。是SubprocessError 的子类。当使用check_call() 或check_output() 运行一个进程,返回一个非0退出状态码时,引发该异常。 |
只用在Windows平台的subprocess.STARTUPINFO
类、和8个常量。本处省略,可参考官方文档。
简单示例1:
一个进程通信的例子:打开一个只有网址的文本文件(adress.txt),读取其中的adress,然后进行ping操作,并将ping结果写入ping.txt文件中。
首先创建一个子进程child,传入要执行的shell命令,并获得标准输出流、返回码等。
import os
import subprocess
class Shell:
def runCmd(self, cmd):
child = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
sout, serr = child.communicate()
return child.returncode, sout, serr, child.pid
shell = Shell()
with open('address.txt', 'r') as f:
addressList = f.readlines()
with open('ping.txt', 'a') as f:
for i in addressList:
i = i.strip()
result = shell.runCmd('ping' +i)
if result[0] == 0:
w = i +' : 0'
f.write(w +'\n')
else:
w = i +' : 1'
f.write(w +'\n')
address.txt
www.google.com.hk
运行上述程序,得到结果是:ping.txt
www.google.com.hk : 1
简单示例2:在Windows下,打印当前设备连接状态(adb devices
命令)
from shutil import which
import subprocess
import re
def devices(states=['device', 'offline']):
"""
Returns:
[($serial1, "device"), ($serial2, "offline")]
eg:[('10.2.0.140:5555', 'device')]
"""
output = subprocess.check_output([which("adb"), 'devices'], encoding='utf-8')
pattern = re.compile(r'(?P<serial>[^\s]+)\t(?P<status>device|offline)')
matches = pattern.findall(output)
return [(m[0], m[1]) for m in matches]
if __name__ == "__main__":
devices()
运行结果:
[('10.2.0.140:5555', 'device')]
综上:
在编码时,当考虑到该用哪个模块、哪个函数来执行命令 与系统及系统进行交互时,首先要清楚的是所用的Python version。
- Python 2.4 引入了
subprocess
模块 用来替换os.system()
、os.popen()
、os.spawn*()
等函数、及Python 2.7的command
模块。即若使用的是Python 2.4及以上版本,就应该使用subprocess
模块。 - 若使用的是Python >=2.4 and <=3.5,Python官方给出的建议是使用
subprocess.call()
函数。Python 2.5新增了一个subprocess.check_call()
函数;Python 2.7新增了subprocess.check_output()
函数。 - 若使用的是Python >=3.5,Python官方给出的建议是尽量使用
subprocess.run()
函数。 - 当
subprocess.call()
、subprocess.check_call()
、subprocess.check_output()
、subprocess.run()
这些高级函数无法满足需求时,可使用subprocess.Popen类
来实现所需要的复杂功能。
如需转载请注明出处。