Python模塊之subprocess

一 簡介
      在使用Python 開發MySQL自動化相關的運維工具的時候,遇到一些有意思的問題,本文介紹Python的 subprocess 模塊以及如何和MySQL交互具體操做,如啓動 ,關閉 ,備份數據庫。
二 基礎知識
     Python2.4引入
subprocess模塊來管理子進程,能夠像Linux 系統中執行shell命令那樣fork一個子進程執行外部的命令,而且能夠鏈接子進程的output/input/error管道,獲取命令執行的輸出,錯誤信息,和執行成功與否的結果。
Subprocess 提供了三個函數以不一樣的方式建立子進程。他們分別是

2.1 subprocess.call() 
父進程等待子進程完成,而且返回子進程執行的結果 0/1
其實現方式

python

  1. def call(*popenargs, **kwargs):
    mysql

  2.     return Popen(*popenargs, **kwargs).wait()sql

例子 
shell

  1. >>> out=subprocess.call(["ls", "-l"])
    數據庫

  2. total 88
    flask

  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
    app

  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

2.2 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

2.3 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 ...

相關文章
相關標籤/搜索