【Python】模块之subprocess

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 一 简介    在使用Python 开发MySQL自动化相关的运维工具的时候,遇到一些有意思的问题,本文介绍Python的 subprocess 模块以及如何和MySQL交互具体操作,如启动 ,关闭 ,备份数据库。
一 简介
   在使用Python 开发MySQL自动化相关的运维工具的时候,遇到一些有意思的问题,本文介绍Python的 subprocess 模块以及如何和MySQL交互具体操作,如启动 ,关闭 ,备份数据库。
二 基础知识
Python2.4引入 subprocess模块来管理子进程,可以像Linux 系统中执行shell命令那样fork一个子进程执行外部的命令,并且可以连接子进程的output/input/error管道,获取命令执行的输出,错误信息,和执行成功与否的结果。
Subprocess 提供了三个函数以不同的方式创建子进程。他们分别是
subprocess.call() 
父进程等待子进程完成,并且返回子进程执行的结果 0/1
其实现方式
  1. def call(*popenargs, **kwargs):
  2.     return Popen(*popenargs, **kwargs).wait()
例子 
  1. >>> out=subprocess.call(["ls", "-l"])
  2. total 88
  3. drwxr-xr-x 5 yangyi staff 170 1 25 22:37 HelloWorld
  4. drwxr-xr-x 11 yangyi staff 374 12 18 2015 app
  5. -rw-r--r-- 1 yangyi staff 3895 4 19 11:29 check_int.py
  6. ..... 省略一部分
  7. >>> print out
  8. 0
  9. >>> out=subprocess.call(["ls", "-I"])
  10. ls: illegal option -- I
  11. usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]
  12. >>> print out
  13. 1
subprocess.check_call()
父进程等待子进程完成,正常情况下返回0,当检查退出信息,如果returncode不为0,则触发异常
subprocess.CalledProcessError,该对象包含有returncode属性,应用程序中可用try...except...来检查命令是否执行成功。
其实现方式
  1. def check_call(*popenargs, **kwargs):
  2.     retcode = call(*popenargs, **kwargs)
  3.     if retcode:
  4.         cmd = kwargs.get("args")
  5.         raise CalledProcessError(retcode, cmd)
  6.     return 0
例子
  1. >>> out=subprocess.check_call(["ls"])
  2. HelloWorld    check_int.py    enumerate.py    hello.py
  3. >>> print out
  4. 0
  5. >>> out=subprocess.check_call(["ls",'-I']) #执行命令失败的时候回抛出CalledProcessError异常,并且返回结果1
  6. ls: illegal option -- I
  7. usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]
  8. Traceback (most recent call last):
  9.   File "", line 1, in <module>
  10.   File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 540, in check_call
  11.     raise CalledProcessError(retcode, cmd)
  12. subprocess.CalledProcessError: Command '['ls', '-I']' returned non-zero exit status 1
subprocess.check_output()
和 subprocess.check_call() 类似,但是其返回的结果是执行命令的输出,而非返回0/1
其实现方式
  1. def check_output(*popenargs, **kwargs):
  2.     process = Popen(*popenargs, stdout=PIPE, **kwargs)
  3.     output, unused_err = process.communicate()
  4.     retcode = process.poll()
  5.     if retcode:
  6.         cmd = kwargs.get("args")
  7.         raise CalledProcessError(retcode, cmd, output=output)
  8.     return output
例子
  1. >>> out=subprocess.check_output(["ls"]) #成功执行命令
  2. >>> print out
  3. HelloWorld
  4. check_int.py
  5. enumerate.py
  6. flasky
  7. hello.py
  8. >>> out=subprocess.check_output(["ls","-I"])#执行命令出现异常直接打印出异常信息。
  9. ls: illegal option -- I
  10. usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]
  11. Traceback (most recent call last):
  12.   File "", line 1, in <module>
  13.   File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 573, in check_output
  14.     raise CalledProcessError(retcode, cmd, output=output)
  15. subprocess.CalledProcessError: Command '['ls', '-I']' returned non-zero exit status 1
  16. >>>
通过上面三个例子,我们可以看出前面两个函数不容易控制输出内容,在使用subprocess包中的函数创建子进程执行命令的时候,需要考虑
1) 在创建子进程之后,父进程是否暂停,并等待子进程运行。
2) 如何处理函数返回的信息(命令执行的结果或者错误信息)
3) 当子进程执行的失败也即returncode不为0时,父进程如何处理后续流程?

三 subprocess的核心类 Popen() 
  认真的读者朋友可以看出上面三个函数都是基于Popen实现的,为啥呢?因为 subprocess 仅仅提供了一个类,call(),check_call(),check_outpu()都是基于Popen封装而成。当我们需要更加自主的应用subprocess来实现应用程序的功能时,
  我们要自己动手直接使用Popen()生成的对象完成任务。接下来我们研究Popen()的常见用法 ,详细的用法请参考 官方文档
Popen(args, bufsize=0, 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)
这里我们只要掌握常用的参数即可 
args                字符串或者列表,比如 "ls -a" / ["ls","-a"]
stdin/stdout/stderr 为None时表示没有任何重定向,继承父进程,还可以设置为PIPE 创建管道/文件对象/文件描述符(整数)/stderr 还可以设置为 STDOUT 后面会给出常见的用法
shell              是否使用shell来执行程序。当shell=True, 它将args看作是一个字符串,而不是一个序列。在Unix系统,且 shell=True时,shell默认使用 /bin/sh.
                    如果 args是一个字符串,则它声明了通过shell执行的命令。这意味着,字符串必须要使用正确的格式。
                    如果 args是一个序列,则第一个元素就是命令字符串,而其它的元素都作为参数使用。可以这样说,Popen等价于:
                    Popen(['/bin/sh', '-c', args[0], args[1], ...])

与上面第二部分介绍的三个函数不同,subprocess.Popen() fork子进程之后主进程不会等待子进程结束,而是直接执行后续的命令。当我们需要等待子进程结束必须使用wait()或者communicate()函数。举个例子,
  1. import subprocess
  2. sbp=subprocess.Popen(["ping","-c","5","www.youzan.com"])
  3. print "ping is not done"

从执行结果上看,子进程 ping命令并未执行完毕,subprocess.Popen()后面的命令就开始执行了。

Popen常见的函数
Popen.poll()  用于检查子进程是否已经结束,设置并返回returncode属性。
Popen.wait()  等待子进程结束,设置并返回returncode属性。
Popen.communicate(input=None) 与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。 Communicate()返回一个元组:(stdoutdata, stderrdata)。注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。需要注意的是 communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。
Popen.send_signal(signal)  向子进程发送信号。
Popen.terminate() 终止子进程。
Popen.kill() 杀死子进程。
Popen.pid    获取子进程的进程ID。
Popen.returncode  获取进程的返回值, 成功时,返回0/失败时,返回 1。如果进程还没有结束,返回None。
这里需要多做说明的是
对于 wait() 官方提示
  1. Warning This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.
即当stdout/stdin设置为PIPE时,使用wait()可能会导致死锁。因而建议使用communicate
而对于communicate,文档又给出:
  1. Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optionalinput argument should be a string to be sent to the child process, orNone, if no data should be sent to the child.communicate() returns a tuple (stdoutdata, stderrdata).
  2. Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other thanNone in the result tuple, you need to give stdout=PIPE and/orstderr=PIPE too.
  3. Note
  4. The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
communicate会把数据读入内存缓存下来,所以当数据很大或者是无限的数据时不要使用。
那么坑爹的问题来了:当你要使用Python的subprocess.Popen实现命令行之间的管道传输,同时数据源又非常大(比如读取上GB的文本或者无尽的网络流)时,官方文档不建议用wait,同时communicate还可能把内存撑爆,我们该怎么操作?
四 Subprocess 和MySQL 的交互
  纸上来得终觉浅,绝知此事要躬行。自动化运维需求中会有重启/关闭/备份/恢复 MySQL的需求。怎么使用Python的subprocess来解决呢?启动MySQL的命令如下
  1. startMySQL="/usr/bin/mysqld_safe --defaults-file=/srv/my{0}/my.cnf --read_only=1 & ".format(port)
实际上使用child=subprocess.Popen(startMySQL,shell=True,stdout=stdout=subprocess.PIPE),子进程mysql_safe是无任何返回输出的,使用,child.communicate()或者读取stdout 则会持续等待。
需要使用 child.wait()或者child.poll()检查子进程是否执行完成。 
  1. import subprocess,time
  2. def startMySQL(port):
  3.     startMySQL="/usr/bin/mysqld_safe --defaults-file=/srv/my{0}/my.cnf --read_only=1 & ".format(port)
  4.     child=subprocess.Popen(startMySQL, shell=True,stdout=subprocess.PIPE)
  5.     child.poll()
  6.     time.sleep(3) #有些MySQL实例启动可能需要一定的时间
  7.     if child.returncode:
  8.         print "instance {0} startup failed ...".format(port)
  9.     else:
  10.         print "instance {0} startup successed ...".format(port)
  11.     return
  12. root@rac3:~/python# >python 1.py
  13. instance 3308 startup successed ...
五 参考资料
[1] 官方文档 
[2] Python中的subprocess与Pipe 
[3] python类库31[进程subprocess] 
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
14天前
|
存储 开发者 Python
Python中的collections模块与UserDict:用户自定义字典详解
【4月更文挑战第2天】在Python中,`collections.UserDict`是用于创建自定义字典行为的基类,它提供了一个可扩展的接口。通过继承`UserDict`,可以轻松添加或修改字典功能,如在`__init__`和`__setitem__`等方法中插入自定义逻辑。使用`UserDict`有助于保持代码可读性和可维护性,而不是直接继承内置的`dict`。例如,可以创建一个`LoggingDict`类,在设置键值对时记录操作。这样,开发者可以根据具体需求定制字典行为,同时保持对字典内部管理的抽象。
|
16天前
|
存储 缓存 算法
Python中collections模块的deque双端队列:深入解析与应用
在Python的`collections`模块中,`deque`(双端队列)是一个线程安全、快速添加和删除元素的双端队列数据类型。它支持从队列的两端添加和弹出元素,提供了比列表更高的效率,特别是在处理大型数据集时。本文将详细解析`deque`的原理、使用方法以及它在各种场景中的应用。
|
1天前
|
Python
python学习14-模块与包
python学习14-模块与包
|
2天前
|
SQL 关系型数据库 数据库
Python中SQLite数据库操作详解:利用sqlite3模块
【4月更文挑战第13天】在Python编程中,SQLite数据库是一个轻量级的关系型数据库管理系统,它包含在一个单一的文件内,不需要一个单独的服务器进程或操作系统级别的配置。由于其简单易用和高效性,SQLite经常作为应用程序的本地数据库解决方案。Python的内置sqlite3模块提供了与SQLite数据库交互的接口,使得在Python中操作SQLite数据库变得非常容易。
|
7天前
|
索引 Python
「Python系列」Python operator模块、math模块
Python的`operator`模块提供了一系列内置的操作符函数,这些函数对应于Python语言中的内建操作符。使用`operator`模块可以使代码更加清晰和易读,同时也能提高性能,因为它通常比使用Python内建操作符更快。
25 0
|
12天前
|
数据采集 网络协议 API
python中其他网络相关的模块和库简介
【4月更文挑战第4天】Python网络编程有多个流行模块和库,如requests提供简洁的HTTP客户端API,支持多种HTTP方法和自动处理复杂功能;Scrapy是高效的网络爬虫框架,适用于数据挖掘和自动化测试;aiohttp基于asyncio的异步HTTP库,用于构建高性能Web应用;Twisted是事件驱动的网络引擎,支持多种协议和异步编程;Flask和Django分别是轻量级和全栈Web框架,方便构建不同规模的Web应用。这些工具使网络编程更简单和高效。
|
16天前
|
数据采集 数据挖掘 Python
Python中collections模块的Counter计数器:深入解析与应用
在Python的`collections`模块中,`Counter`是一个强大且实用的工具,它主要用于计数可哈希对象。无论是统计单词出现的频率,还是分析数据集中元素的分布情况,`Counter`都能提供快速且直观的结果。本文将深入解析`Counter`计数器的原理、用法以及它在实际应用中的价值。
|
17天前
|
Python
Python中的math和cmath模块:数学运算的得力助手
Python作为一种功能强大的编程语言,提供了丰富的数学运算功能。其中,math和cmath模块就是Python中用于数学运算的重要工具。math模块提供了基本的数学函数和常量,适用于实数运算;而cmath模块则提供了对复数运算的支持,使得Python在数学计算和工程应用中更加灵活和强大。
|
20天前
|
数据挖掘 Python
Python中的datetime模块:轻松拿捏时间操作
Python的`datetime`模块是处理日期和时间的核心工具,包括`date`、`time`、`datetime`、`timedelta`类。它可以创建、操作和格式化日期时间。
18 2
|
22天前
|
Python
Python random模块(获取随机数)常用方法和使用例子
`random`模块在Python中用于生成随机数。
19 0