subprocess
使用Python进行Linux系统管理与运维总是免不了在Python代码中执行shell命令、启动子进程,并捕获命令的输出和退出状态。
而subprocess模块就可以实现, 它最早是在python2.4版本中引入中。
call
call函数的返回值是命令的退出状态码,工程师可以通过退出状态码判命令是否执行成功, 成功返回0,否则返回非0
call函数执行的外部命令以一个字符串列表的形式进行传递
In [31]: status_code = subprocess.call(['ls', '-l']) total 17700 drwxr-xr-x 9 nginx nginx 4096 Nov 10 00:10 nginx-1.12.2 -rw-r--r-- 1 root root 981687 Dec 22 2017 nginx-1.12.2.tar.gz drwxr-xr-x 14 nginx nginx 4096 Nov 10 00:02 zabbix-3.4.4 -rw-r--r-- 1 root root 17132537 Dec 25 2017 zabbix-3.4.4.tar.gz In [32]: status_code Out[32]: 0
如果设置了shell为True,则可以使用一个字符串命令,python将先运行一个shell,在用这个shell来解释整个字符串
In [34]: status_code = subprocess.call('ls -l', shell=True) total 17700 drwxr-xr-x 9 nginx nginx 4096 Nov 10 00:10 nginx-1.12.2 -rw-r--r-- 1 root root 981687 Dec 22 2017 nginx-1.12.2.tar.gz drwxr-xr-x 14 nginx nginx 4096 Nov 10 00:02 zabbix-3.4.4 -rw-r--r-- 1 root root 17132537 Dec 25 2017 zabbix-3.4.4.tar.gz In [35]: status_code Out[35]: 0
模拟执行失败
In [39]: status_code = subprocess.call('exit 1', shell=True) In [40]: status_code Out[40]: 1
check_call
check_call函数的作用与call函数类似,区别在于异常情况下返回的形式不同,对于check_call函数,如果命令执行成功,返回0,如果执行失败,抛出suprocess.CalledProcessError异常
In [41]: status_code = subprocess.check_call('exit 1', shell=True) --------------------------------------------------------------------------- CalledProcessError Traceback (most recent call last) <ipython-input-41-7ec365b00bf2> in <module>() ----> 1 status_code = subprocess.check_call('exit 1', shell=True) /usr/lib64/python2.7/subprocess.pyc in check_call(*popenargs, **kwargs) 540 if cmd is None: 541 cmd = popenargs[0] --> 542 raise CalledProcessError(retcode, cmd) 543 return 0 544 CalledProcessError: Command 'exit 1' returned non-zero exit status 1
check_output
call和check_call函数直接将命令的输出结果输出到命令行终端。在实际工作中,一般会对获取的命令结果进行进一步的处理,或者将命令的输出打印到日志文件中。
In [42]: output = subprocess.check_output(['df', '-h']) In [43]: print(output) Filesystem Size Used Avail Use% Mounted on /dev/sda1 20G 2.3G 18G 12% / devtmpfs 904M 0 904M 0% /dev tmpfs 913M 4.0K 913M 1% /dev/shm tmpfs 913M 9.7M 903M 2% /run tmpfs 913M 0 913M 0% /sys/fs/cgroup tmpfs 183M 0 183M 0% /run/user/0 In [44]: lines = output.split('\n') In [46]: lines Out[46]: ['Filesystem Size Used Avail Use% Mounted on', '/dev/sda1 20G 2.3G 18G 12% /', 'devtmpfs 904M 0 904M 0% /dev', 'tmpfs 913M 4.0K 913M 1% /dev/shm', 'tmpfs 913M 9.7M 903M 2% /run', 'tmpfs 913M 0 913M 0% /sys/fs/cgroup', 'tmpfs 183M 0 183M 0% /run/user/0', ''] In [47]: for line in lines[1:-1]: ...: if line: ...: print(line.split()[-2]) ...: 12% 0% 1% 2% 0% 0%
check_ouput函数通过返回值来返回命令的执行结果,显然无法像call函数一样通过返回退出状态码表示异常情况。因此,check_output函数通过抛出一个subprocess.CalledProcessError异常来表示命令执行出错,如下:
try: output = subprocess.check_output(['cmd', 'arg1', 'arg2']) except subprocess.CalledProcessError as e: output = e.output code = e.returncode
默认情况下,check_ouput命令只会捕获命令的标准输出。如果想捕获命令的错误输出,需要将输出重定向到标准输出。如下所示:
In [9]: try: ...: subprocess.check_output(['rm', '-f', '/tmp/root.txt'], stderr=subprocess.STDOUT) ...: except subprocess.CalledProcessError as e: ...: output = e.output ...: code = e.returncode ...: In [10]: output Out[10]: 'rm: cannot remove \xe2\x80\x98/tmp/root.txt\xe2\x80\x99: Operation not permitted\n'
Popen类
当以上便利函数无法满足业务的需求时,也可以直接使用Popen类。Popen类更具有灵活性,能够通过它处理更多复制的情况。
下面的函数对Popen执行shell命令进行封装,封装以后,只要将需要执行的shell命令传递给该函数即可。当命令执行成功时,将返回命令退出的状态码和标准输出,当命令执行失败时,将返回退出状态码和错误输出
import sys import subprocess def execute_cmd(cmd): p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if p.returncode != 0: return p.returncode, stderr return p.returncode, stdout print(execute_cmd(sys.argv[1]))