Python3.X subprocess、os.system/os.popen/os.spawn*、commands:与系统进行交互,如测试时常用的adb命令

如需转载请注明出处。
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新增加encodingerrors参数。
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.7commands模块的两个遗留函数。它们隐式地调用系统shell,并且不保证其他函数所具有的安全性和异常处理的一致性。而且,两者从Python 3.3.4才开始支持Windows平台。

上述函数中常见参数说明:

参数 说明
args 要执行的shell命令(可理解为 args指定的一个外部程序),默认是一个字符串序列,如['df', '-Th']('df', '-Th'),也可是一个字符串,如'df -Th',但此时要把shell参数的值为True
shell 如果shellTrue,那么指定的命令将通过shell执行。如果我们需要访问某些shell的特性,如管道、文件名通配符、环境变量扩展功能,这将是非常有用的。当然,Python本身也提供了许多类似shell的特性的实现,如globfnmatchos.walk()os.path.expandvars()os.expanduser()shutil等。
check 如果check参数的值是True,且执行命令的进程以非0状态码退出,则会抛出一个CalledProcessError的异常,且该异常对象会包含参数、退出状态码、以及stdoutstderr(如果它们有被捕获的话)。
stdoutstderr 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,那么stdoutstderr将会被整合到这一个属性中,且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,12之外的所有文件描述符都将会在子进程执行之前被关闭。
  • shell: 该参数用于标识是否使用shell作为要执行的程序,如果shell值为True,则建议将args参数作为一个字符串传递而不要作为一个序列传递。
  • cwd: 如果该参数值不是None,则该函数将会在执行这个子进程之前改变当前工作目录。
  • env: 用于指定子进程的环境变量,如果env=None,那么子进程的环境变量将从父进程中继承。如果env!=None,它的值必须是一个映射对象。
  • universal_newlines: 如果该参数值为True,则该文件对象的stdinstdoutstderr将会作为文本流被打开,否则他们将会被作为二进制流被打开。
  • startupinfocreationflags: 这两个参数只在Windows下有效,它们将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如主窗口的外观,进程优先级等。

②、subprocess.Popen类的实例(对象)可调用的方法:

方法名 说明
Popen.poll() 用于检查子进程(命令)是否已经执行结束,没结束返回None,结束后返回状态码。
Popen.wait(timeout=None) 等待子进程结束,并返回状态码;如果在timeout指定的秒数之后进程还没有结束,将会抛出一个TimeoutExpired异常。
Popen.communicate(input=None, timeout=None) 该方法可用来与进程进行交互,比如发送数据到stdin,从stdoutstderr读取数据,直到到达文件末尾。
Popen.send_signal(signal) 发送指定的信号给这个子进程。
Popen.terminate() 停止该子进程。
Popen.kill() 杀死该子进程。

其中,详解communicate()方法:该方法会阻塞父进程,直到子进程完成。

  • 该方法中的可选参数 input 应该是将被发送给子进程的数据,或者如没有数据发送给子进程,该参数应该是Noneinput参数的数据类型必须是字节串,如果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对象的stdinstdoutstderr参数,并指示将使用特殊文件os.devnull。Python 3.3中新增的。
subprocess.PIPE 在创建Popen对象时,subprocess.PIPE可以初始化为stdin, stdoutstderr的参数,表示与子进程通信的标准输入流,标准输出流以及标准错误。
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类来实现所需要的复杂功能。

参考:
官方文档3.6----subprocess

如需转载请注明出处。

猜你喜欢

转载自blog.csdn.net/weixin_38256474/article/details/83117270
今日推荐