一 学习目的
说明:写这部分的'原因'是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的返回值
++++++++++++++其'返回值'的信息++++++++++++++
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
② run单条命令最佳实践
② run脚本最佳实践
报错原因
③ run多条命令最佳实践
思路: list方式通过"&&"组合成'大字符串(shell=True)' -->'推荐' -->'shell=true'还使用shell的'高级'功能
备注: 这里'不再'演示Python的两种'报错'
四 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'又是'一个'字符串'的话,必须是"程序的路径"才可以,这种方式'不能传入'任何参数了
② bufsize
③ executable
备注:一般'不用',args字符串或列表'第一项'表示程序名
当指定shell='True'时,executable用来'修改默认'的shell环境,比如:executable='/bin/bash'
1. 通常地一个应用程序'默认'地连接有'3个io流':
分别为stdin标准'输入'流、stdout标准'输出'流、stderr标准'错误输出'流
在程序中我们可以使用它们的'句柄0,1,2'来使用它们
2. 程序与流的'工作过程'如下:
从终端的输入通过'标准输入'流传给程序、'程序的输出'传给'标准输出'流、程序的错误传给标准错误输出流。
3. 程序的流的'重定向[redirect]':
例如:将程序的输入'重定向'到一个文件file1,将'输出'和'错误输出'重定向到文件file2
语句如下:program < file1 > file2 2>&1
4. 管道pipe:
用来将一个'程序的标准输出'作为'另一个程序的输入':例如:program1 | program2
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=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的输出是"字符串"
(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()))