Python使用subprocess開啓新進程之旅

Python 使用subprocess開啓新進程之旅

版本說明:Python2.7

感覺好久沒有寫博客了,最近接連兩項工作,忙的不亦樂乎,難得空檔期,做一下筆記總結。同樣是工作中遇到的問題,簡單描述一下:有這樣一段腳本,它執行時間比較長,而且不斷地有標準輸出,需要Flask提供REST服務去異步執行這段腳本,並且實時捕獲標準輸出,通過WebSocket推送給前臺。關鍵點:異步調用,實時獲取標準輸出,並且可能涉及到實時交互。當時解決這個問題,一開始使用的是Python的輸出重定向sys.stdout,然後重寫write方法,將輸出寫到Queue裏,然後起一個線程輪詢Queue,但是發現輸出不全,而且對於輸出的控制不理想。重要的是無法進行交互。後來發現使用Python的subprocess能夠很好的解決問題。

1 關於subprocess

官網:https://docs.python.org/2/library/subprocess.html

subprocess模塊允許我們生成一個新進程,連接到新進程的輸出、輸出、錯誤管道,並獲取它們的返回碼。該模塊旨在替換幾個比較舊的模塊和功能

os.system
os.spawn*
os.popen*
popen2.*
commands.*

那麼我們到底可以使用subprocess來做什麼呢?執行一個程序,執行一個shell命令,調用一個腳本等。

2 使用subprocess

2.1 call()

call()方法能便捷的調用一個程序,並得到執行的返回碼。該方法是同步執行,需要等待命令執行完成,並且stdout不能指向PIPE,默認繼承父進程的輸出。

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

args可以是字符串,也可以是列表,如果是字符串的話,會被當做shell命令,必須指定shell=True。args爲列表的話,列表第一個元素會認爲是程序路徑,後面元素爲參數。如我要執行"ls ."這個命令,可以使用call(“ls .”,shell=True),它相當於call([’/bin/sh’,’-c’,‘ls .’])。我們也可以直接使用call([‘ls’,’.’])該方法返回值是一個返回碼,表示該進行執行的狀態,如果返回0表示正常,返回其它狀態碼錶示異常。

如:我們使用call方法,執行命令ls .查看當前路徑所有的文件

"""
使用call()方法執行一個命令
返回值爲0說明執行成功,此命令不能將輸出定向到PIPE
非異步
"""
return_code = subprocess.call('ls .', shell=True)
print return_code

2.2 check_call()

check_call()方法與call類似,不同點在於,該方法會檢查返回狀態,如果返回碼不等於0將拋出CalledProcessError異常。

subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

該方法的參數與call()相同,返回值也是返回碼。

"""
使用check_all()方法執行一個命令
返回碼不爲0,將會拋出CalledProcessError異常
非異步
"""
subprocess.check_call(['ls', '.'], shell=False)
subprocess.check_call('exit 1', shell=True)

2.3 check_output()

上述的兩個方法,返回值都是一個狀態碼,表示該進程的結果狀態,而輸出信息都是定向到父進程的標準輸出裏了,有時候我們需要拿到執行某條命令的結果,我們可以使用check_output()方法。它可以返回執行的結果,如果執行失敗同樣會拋出CalledProcessError異常,此方法也是同步的。

subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)

該方法前面幾個參數與上述幾個方法相同,universal_newlines方法表示是否換行,默認爲False,如果爲True會在返回的輸出後面添加"\n"進行換行。

"""
使用check_output()方法執行一個命令
返回碼不爲0,將會拋出CalledProcessError異常
運行正常,將會把標準輸出結果返回,
要想將標準錯誤返回,可以使用stderr=subprocess.STDOUT
非異步
"""
output = subprocess.check_output(['echo', 'hello world'], universal_newlines=True)
print output

2.4 Popen 構造器

subprocess可以使用Popen構造,功能更強大,使用更靈活,可以做到異步調用,實時交互等。

subprocess.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)

2.4.1 參數說明

Popen構造器所需要的參數列表如下所示:

參數名稱 說明
args 字符串或者是列表,表示被調用程序的路徑和參數
bufsize 0 表示無緩存
1 表示行緩衝
其它正值 表示緩衝區大小
負值 採用系統默認緩衝(全緩衝)
executable 可執行程序,如果爲None取args列表的第一個值
stdin
stdout
stderr
None 沒有任何重定向,繼承父進程
PIPE 創建管道
文件對象
文件描述符(整數)
stderr 還可以設置爲STDOUT
preexec_fn 鉤子函數,在fork和exec之前執行(unix)
close_fds unix下執行新進程之前是否關閉0/1/2之外的文件
windows下不繼承是繼承父進程的文件描述符
shell unix下相當於在args前面添加了"/bin/sh" “-c"
window下相當於添加"cmd.exe /c”
cwd 設置工作目錄
env 設置環境變量
universal_newlines 添加換行符"\n"
startupinfo window下傳遞給CreateProcess的結構體
creationflags window下,傳遞CREATE_NEW_CONSOLE創建自己的控制檯窗口

2.4.2 方法說明

使用Popen構造器產生的對象具有以下方法。

方法名稱 說明
poll() 檢查子進程是否已經結束,設置並返回returncode屬性,非結束返回None
wait() 阻塞主進程等待子線程完成,返回returncode,注意:如果子進程輸出了大量數據到stdout或者stderr的管道,並達到了系統pipe的緩存大小的話,子進程會等待父進程讀取管道,而父進程此時正wait着的話,將會產生傳說中的死鎖,後果是非常嚴重滴。建議使用communicate() 來避免這種情況的發生。
communicate(input=None) 和子進程交互,發送數據到stdin,並從stdout和stderr讀數據,直到收到EOF,阻塞,一直等待子進程結束。input輸出要爲字符串。該方法返回一個元組(stdoutdata,stderrdata)。注意:要給子進程的stdin發送數據,則Popen的時候,stdin要爲PIPE;同理,要可以接收數據的話,stdout或者stderr也要爲PIPE。
send_signal(signal) 在子進程發送signal信號,注意:window下目前只支持發送SIGTERM,等效於小面的terminate()
terminate() 停止子進程。Posix下是發送SIGTERM信號。windows下是調用TerminateProcess()這個API。
kill() 殺死子進程。Posix下是發送SIGKILL信號。windows下和terminate() 無異。
stdin 如果stdin 參數是PIPE,此屬性就是一個文件對象,否則爲None 。
stdout 如果stdout參數是PIPE,此屬性就是一個文件對象,否則爲None。
stderr 如果stderr參數是PIP,此屬性就是一個文件對象,否則爲None。
pid 子進程的進程號。注意,如果shell 參數爲True,這屬性指的是子shell的進程號。
returncode 子程序的返回值,由poll()或者wait()設置,間接地也由communicate()設置。
如果爲None,表示子進程還沒終止。
如果爲負數-N的話,表示子進程被N號信號終止。(僅限unix)

3 應用Demo

3.1 調用腳本並獲取實時輸出

假如我有如下名爲callme.py的腳本,我想使用subprocess實時調用,該怎麼寫?

# encoding: utf-8
"""
@author : shirukai
@date : 2019-05-16 10:03
用來測試subprocess腳本調用
"""
import time

if __name__ == '__main__':
    for i in range(10):
        print "I was called {0} times.".format(str(i + 1))
        time.sleep(5)

可以這樣寫:

    process = subprocess.Popen(['python', 'callme.py'], stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
    while process.poll() is None:
        print process.stdout.readline()

https://stackoverflow.com/questions/874815/how-do-i-get-real-time-information-back-from-a-subprocess-popen-in-python-2-5

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章