Pthon(三十八)subprocess模块

一    学习目的

说明:写这部分的'原因'是shell本身功能'太单一',无法像'python'一样对'字符串'、'文件解析'、'正则表达式'-->'游刃有余'

补充:探讨'python'执行'shell'命令或'脚本'的方式

+++++++++++'语言应用场景'+++++++++++

1)'琐碎'任务一次性的任务交给shell

2)注定要'扩展',代码量'不小','要维护'的任务交给python

3)需要'效率'的工作交给C

备注:学习'subprocess'模块类比'linux'相关的'实现'方法

二    subprocess

需求:如何'使用'python代码来执行linux的命令?

​具体:subprocess'模块'来执行命令

Python subprocess中的run方法

中文文档

(1)使用背景

需求: 在Python端'调用'大数据的'shell脚本',所以需要用到'Python'来执行shell脚本,,因此学习下'subprocess模块'文档

(2)官网描述

1) subprocess模块用于创建'子进程'

2) 这个模块用于'替换旧版本中'的一些模块,如:os.'system'、os.spawn*、os.popen*、os.popen*、popen2.*, commands.*

3) subprocess允许你能'创建很多子进程',创建的时候能能'指定'子进程和子进程的'输入'、'输出'、错误输出管道,执行后能'获取'输出'结果(result)'和执行'状态(status)'

(3)run方法

 subprocess.run()函数是python'3.5中新增'的一个高级函数

备注: 如果要'使用'该方法的话指定'python的解释器'

补充: 随着'python2.7'不再支持,进入'python3.x'时代;在python3.5之后的版本中,'官方文档'中提倡通过subprocess.run()函数替代其他函数来

①    args参数

+++++++++++++(1)'args'+++++++++++++

args: 要执行的'shell命令'

备注:args可选的方式与'shell=(True|False)'强相关

②    shell参数 

+++++++++++++(2)'shell'为'True'和'False'的区别+++++++++++++

1)shell='True'参数会让subprocess.run接受'字符串类型'或'列表'的变量作为命令,并'调用shell'去'解释'和'执行'这个字符串

['ls', '-l'] 或 'ls -l'

2)shell='False'时["默认值"],subprocess.run只'接受数组变量'作为命令

only  ['ls', '-l']

特点:将'列表(list)'的'第一个(first)'元素作为命令,'剩下'的全部作为'该命令'的参数

补充:官方'不推荐'使用shell=True

③      输出相关

1)stdout、stdin、stderr:分别表示子程序标准输出、标准输入、标准错误

备注:有效值可为subprocess.'PIPE'、一个有效的'文件描述符'、文件'对象'或'None'

2)若为subprocess.PIPE:代表打开通向标准流的管道,创建一个'新的管道(pipe)'

3)若为None:表示'没有任何'重定向,子进程会'继承'父进程

4)stderr也可为subprocess.STDOUT:表示将子程序的'标准错误输出'重定向到了'标准输出'

    "2>&1"

④    check参数

1) check:check为'True'时,表示执行命令的进程以'非0状态码退出'时会抛出'subprocess.CalledProcessError'异常

2) check'为False'时,状态码为'非0退出时'不会'抛'出异常

类比linux的'set -e'

⑤    cwd

cwd:'默认值'为None,'若非None',则表示将会在执行这个子进程之前'改变'当前工作目录 -->'WORKDIRECTORY'

⑥   env

用于指定'子进程'的环境变量

1)默认值为'None',那么子进程的环境变量将从父进程中'继承'

2)若env'非None',则表示子程序的环境变量'由env值'来设置,它的值必须是一个'dict(字典)'

change workdirectory --> '重点'

⑦    universal_newlines

universal_newlines: 不同系统的'换行符'不同;若'True',则该文件对象的stdin,stdout和stderr将会以'文本流'方式打开、'否则'以'二进制流'方式打开

⑧    不常用的参数

bufsize:指定'缓冲策略',0表示'不缓冲',1表示'行缓冲',其它整数表示'缓冲区大小',负数表示使用系统'默认'值0;

⑨    run的返回值

Pthon(三十八)subprocess模块_第1张图片

++++++++++++++其'返回值'的信息++++++++++++++

1)返回的是subprocess.CompleteProcess类的'实例',它表示的是一个'已结束进程'的状态信息

2)包含'属性'如下:

    args --> 用于'加载该进程的参数',这可能是一个'列表'或一个'字符串'

    returncode  --> 子进程的的'退出状态码',通常情况下,'退出状态码0'表示进程成功运行了,一个'负值-N'表示这个子进程'被信号N'终止了

    备注:若'执行成功(success)',则returncode为0;若执行'失败',则returncode为1

3)'可能出现'-->'stdout'参数

1、若run方法'传参时'-->'未指定'stdout,则'命令执行后'的结果输出到'屏幕上',函数返回值CompletedProcess中包含有args和returncode

2、若'指定有'stdout,则命令执行后的结果'输出到stdout'中,函数返回值CompletedProcess中,'包含'有args、returncode和'stdout'


4) 需求:若想获取args命令执行后的'输出结果'

备注:命令为:output = subprocess.run(args, stdout=subprocess.PIPE).stdout

(3)call方法

1)  在python'3.5之前'的版本中,基本'通过subprocess.call()'来使用subprocess模块的功能

2)  效果-->执行'指定的命令',返回命令'执行状态';功能'类似于'os.system(cmd)-->'了解即可'

补充: subprocess.run()、subprocess.call()、subprocess.check_call()都是通过对'subprocess.Popen的封装'来实现的高级函数

三    实践

需求: 'python'执行1条、'多条'linux命令的'实践'

①  os.system

Pthon(三十八)subprocess模块_第2张图片

Pthon(三十八)subprocess模块_第3张图片

Pthon(三十八)subprocess模块_第4张图片

②    run单条命令最佳实践

Pthon(三十八)subprocess模块_第5张图片

②    run脚本最佳实践

Pthon(三十八)subprocess模块_第6张图片

报错原因

Pthon(三十八)subprocess模块_第7张图片

③    run多条命令最佳实践

思路: list方式通过"&&"组合成'大字符串(shell=True)' -->'推荐' -->'shell=true'还使用shell的'高级'功能

备注: 这里'不再'演示Python的两种'报错'

Pthon(三十八)subprocess模块_第8张图片

Pthon(三十八)subprocess模块_第9张图片

四    subprocess.Popen

(1)整体功能选项

++++++++++++++subprocess模块'定义了一个类': Popen++++++++++++++

说明:比较重要的用''符号包括,体会'默认'选项

class subprocess.Popen(

   'args',bufsize=-1,'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
)
备注:'不同'版本,'POpen'参数有'差异'

(2)参数含义

①    args

当初最感到"困扰"的就是 args 参数;可以是一个字符串,可以是一个列表

subprocess.Popen(["gedit","abc.txt"])   -->默认是'shell=False'

subprocess.Popen("gedit abc.txt")

强调:如果"shell=False"是前提,args'又是'一个'字符串'的话,必须是"程序的路径"才可以,这种方式'不能传入'任何参数了

Pthon(三十八)subprocess模块_第10张图片

②  bufsize

Pthon(三十八)subprocess模块_第11张图片

Pthon(三十八)subprocess模块_第12张图片

③  executable

备注:一般'不用',args字符串或列表'第一项'表示程序名

当指定shell='True'时,executable用来'修改默认'的shell环境,比如:executable='/bin/bash'

Pthon(三十八)subprocess模块_第13张图片

④ 
Pthon(三十八)subprocess模块_第14张图片

Pthon(三十八)subprocess模块_第15张图片

1.  通常地一个应用程序'默认'地连接有'3个io流':

     分别为stdin标准'输入'流、stdout标准'输出'流、stderr标准'错误输出'流

     在程序中我们可以使用它们的'句柄0,1,2'来使用它们

2.  程序与流的'工作过程'如下:

从终端的输入通过'标准输入'流传给程序、'程序的输出'传给'标准输出'流、程序的错误传给标准错误输出流。

3.  程序的流的'重定向[redirect]':

     例如:将程序的输入'重定向'到一个文件file1,将'输出'和'错误输出'重定向到文件file2

     语句如下:program < file1 > file2 2>&1

4.  管道pipe:

用来将一个'程序的标准输出'作为'另一个程序的输入':例如:program1 | program2 

Pthon(三十八)subprocess模块_第16张图片

1) 如果stdin设置为PIPE,此时的stdin其实是个file对象,用来提供输入到新创建的子进程

常用1:stdin=NONE -->"默认"

常用2:stdin=child1.output -->"前一个命令的输出作为下一个命令的输入"

2) 如果'stdout设置为PIPE',此时stdout其实是个'file对象',用来'保存'新创建的'子进程的输出'

常用:'stdout=subprocess.PIPE'

3) 如果stderr设置为PIPE,此时的stderr其实是个file对象,用来保存新创建的子进程的错误输出

常用:'stderr=subprocess.PIPE'

⑤  preexec_fn 

subprocess.Popen(shell_args, preexec_fn=os.setsid)

++++++++++'了解即可'++++++++++

python 3.2 之后 subprocess.Popen '新增'了一个选项 start_new_session,Popen(args, start_new_session=True) 即'等效'于 preexec_fn=os.setsid 

⑥  shell=False|True

++++++"shell=True"优缺点++++++

优点:args是'str'类型,可以使用'任何'的shell相关符号,'多个命令'使用场景

缺点:'不安全'

subprocess.Popen("ls -al | awk '{print $2}'", shell=True)

+++++++++"等价"+++++++++

subprocess.Popen(["/bin/sh", "-c", "ls -al | awk '{print $2}'"])

++++++"shell=False"特点["默认"]++++++

特点:args必须是'list[第一个是命令,后续是其参数]',虽然'安全',但它一次'只能'执行一个命令

建议:subprocess.Popen(['jdk.sh',"arg1",...],shell=False)

说明1:通过上述'脚本'方式可以执行'多条'命令

说明2:方式2见'下面的案例'

⑦   env

env:它描述的是'子进程的环境变量';默认'None',子进程的环境变量将'从父进程继承'而来

⑧   其它参数见subprocess.run,不再细讲

universal_newlines: 该参数影响的是'输入与输出'的'数据格式'

1) 比如它的值'默认为False',此时stdout和stderr的输出是'字节'序列

2) 当该参数的值设置为'True'时,stdout和stderr的输出是"字符串"

Pthon(三十八)subprocess模块_第17张图片

(3)subprocess.Peopen工作原理

在*nix下,当shell=False(默认)时,Popen使用os.execvp()来执行'子程序'

(4) communicate

1) 官方文档里'推荐'使用 Popen.communicate() '避免死锁',而不是'wait'

    功能:来'等待'外部程序执行'结束'

2) 特点:这个方法会把'命令执行的输出放在内存',而'不是管道'里,所以这时候'上限就和内存'大小有关了

    getconf PAGESIZE 或ulimit -a 看 'pipe size'页内存
 
3) 要获得程序'返回值[命令状态码]',可以在调用 Popen.communicate() '之后'取 Popen.returncode 的值

4) Popen.communicate() 来等待外部程序执行结束

特点:会一直'等到进程退出[有超时时间]',并将'标准输出'和'标准错误输出'返回,这样就可以得到'子进程'的输出了

process=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

备注:返回的是'Popen类'的对象

out, err=process.communicate(timeout=120) -->"执行,可以设置超时时间[s],防止死锁"

++++++++++ "命令输出的相关操作"  ++++++++++

需求1:获取'命令的输出' -->out

备注:形如b'    269 ESTABLISHED\n     66 LISTEN\n' -->'不可读,尤其是原始字符串包含中文或非ASCII字符'

说明:'b'则表明它是一个'字节'序列

方式1: "success: {}".format(out,decode()) --> 解码,"返回可读的字符串"

方式2: bytes.decode(out)  -->转化为'bytes'对象

附加:两种效果一样都是'返回字符串',只不过'方式1'多了个字符串拼接

++++++++++ "常用方式"  ++++++++++

stdout=bytes.decode(out)

stderr=bytes.decode(err)

result=[stdout,stderr.process.returncode]

说明:包含'正确输出','错误输出','返回状态码'信息

(5)  shell=False执行多个命令

shlex 模块最常用的是 split() 函数,用来'分割字符串',通常与 'subprocess 结合'使用

备注: split 函数提供了和'shell 处理命令行'参数时'一致'的分隔方式

shlex细讲 

python中的enumerate()函数

import shlex
import subprocess
 
def multiple_cmd_exec(cmds):
	process_list = []
	try:
        # for循环
		for index, cmd in enumerate(cmds):
			if index == 0:
				_p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
			else:
                # 管道符前一个命令的'输出'作为下一个命令的'输入'
				_p = subprocess.Popen(cmd, stdin=process_list[index-1].stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            # 顺序保存'每个管道符'的输出
			process_list.append(_p)
		out, err = process_list[-1].communicate()
        # 根据最后一个命令的'状态码'
		if process_list[-1].returncode == 0:
			print("multiple_cmd_exec success:\n{}".format(out.decode()))
		else:
			print("multiple_cmd_exec err:\n{}".format(err.decode()))
		return out.decode()
	except Exception as error:
		print(error)

# "ls -al | awk '{print $2}'"
# [['ls','-al'],['awk','{print $2}']]

def change_cmds_format(args):
	print("Enter change_cmds_format")
	cmds = []
	result = []
	if '|' in args:
        # 这里使用"1"作为分割符
		cmds = args.split('|')
	else:
		cmds.append(args)
	for cmd in cmds:
        # 备注:引号必须'成对'
		cmd = shlex.split(cmd)
		result.append(cmd)
	return result
	
cmd = "netstat -ant | awk '{print $6}' | sort|uniq -c|sort -rn"
cmds = change_cmds_format(cmd)
ret =  multiple_cmd_exec(cmds)
print(ret)

(6)上述报错

print 'shlex.split(line)'

File "/home/siddhant/sid/.local/lib/python2.7/shlex.py", line 279, in split

return list(lex)

File "/home/siddhant/sid/.local/lib/python2.7/shlex.py", line 269, in next

token = self.get_token()

File "/home/siddhant/sid/.local/lib/python2.7/shlex.py", line 96, in get_token

raw = self.read_token()

File "/home/siddhant/sid/.local/lib/python2.7/shlex.py", line 172, in read_token

raise ValueError, "No closing quotation"

'ValueError: No closing quotation'  --> "引号不成对匹配,就是'|'的错误分割"

(7)解决方案

考虑: awk -F'|' 或 grep -E ('a|b') 这种形式

grep -E '30200|30201' <==> grep -e '30200' -e '30201' <==> grep -E '3020[0-1]'

形如:"netstat -ant | awk '{print $6}' | sort|uniq -c|sort -rn"

最佳实践: "netstat -ant &&& awk '{print $6}' &&& sort &&& uniq -c &&& sort -rn"

解读:使用特殊符号"&&&",'独一无二'可以"避免冲突"

(8)输出

stdout,stderr=subprocess.communation()

stdout=bytes.decode(stdout)

++++++++"两种方式对比"++++++++

print("multiple_cmd_exec success:\n{}".format(out.decode()))

你可能感兴趣的:(python基础知识,subprocess)