Python之路(第二十篇) subprocess模块
subprocess英文意思:子進程
那什么是進程呢?
(一)關于進程的相關理論基礎知識
進程是對正在運行程序的一個抽象,進程的概念起源于操作系統,是操作系統最核心的概念,操作系統的其他所有內容都是圍繞進程的概念展開的。
所以想要真正了解進程,必須事先了解操作系統.
?
程序員無法把所有的硬件操作細節都了解到,管理這些硬件并且加以優化使用是非常繁瑣的工作,這個繁瑣的工作就是操作系統來干的,有了他,程序員就從這些繁瑣的工作中解脫了出來,只需要考慮自己的應用軟件的編寫就可以了,應用軟件直接使用操作系統提供的功能來間接使用硬件。
?
精簡的說的話,操作系統就是一個協調、管理和控制計算機硬件資源和軟件資源的控制程序。
?
?
?
操作系統所處的位置細說的話,操作系統應該分成兩部分功能:
?
-
隱藏了丑陋的硬件調用接口,為應用程序員提供調用硬件資源的更好,更簡單,更清晰的模型(系統調用接口)。應用程序員有了這些接口后,就不用再考慮操作硬件的細節,專心開發自己的應用程序即可。例如:操作系統提供了文件這個抽象概念,對文件的操作就是對磁盤的操作,有了文件我們無需再去考慮關于磁盤的讀寫控制(比如控制磁盤轉動,移動磁頭讀寫數據等細節).
?
-
將應用程序對硬件資源的競態請求變得有序化例如:很多應用軟件其實是共享一套計算機硬件,比方說有可能有三個應用程序同時需要申請打印機來輸出內容,那么a程序競爭到了打印機資源就打印,然后可能是b競爭到打印機資源,也可能是c,這就導致了無序,打印機可能打印一段a的內容然后又去打印c...,操作系統的一個功能就是將這種無序變得有序。
?
進程是操作系統提供的最古老也是最重要的抽象概念之一。
?
什么是進程
進程(Process)是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。
狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。
廣義定義:進程是一個具有一定獨立功能的程序關于某個數據集合的一次運行活動。它是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。
?
簡單的來說就是程序僅僅只是一堆代碼而已,而進程指的是程序的運行過程,就是執行一個一系列程序代碼的過程。
?
進程和程序的區別
-
-
程序可以作為一種軟件資料長期存在,而進程是有一定生命期的。
-
程序是永久的,進程是暫時的。
?
(二)進程、父進程、子進程
父進程:如果進程a創建了進程b,那么進程a就是進程b的父進程,反之進程b就是進程a的子進程。
?
父進程定義在計算機領域,父進程(Parent Process)指已創建一個或多個子進程的進程。
父進程和子進程的關系是管理和被管理的關系,當父進程終止時,子進程也隨之而終止。但子進程終止,父進程并不一定終止。
進程得到的是除了代碼段是與父進程共享的意外,其他所有的都是得到父進程的一個副本,子進程的所有資源都繼承父進程,得到父進程資源的副本,既然為副本,也就是說,二者并不共享地址空間。,兩個是單獨的進程,繼承了以后二者就沒有什么關聯了,子進程單獨運行。
?
?
(三)subprocess模塊
任何操作系統上都可以通過命令行指令與操作系統進行交互,比如Linux平臺下的shell、Windows下的cmd命令行。
Python來完成這些命令行指令的執行呢?另外,我們應該知道的是命令行指令的執行通常有兩個我們比較關注的結果:
命令執行的狀態碼--表示命令執行是否成功
命令執行的輸出結果--命令執行成功后的輸出
?
?
早期的Python版本中,我們主要是通過os.system()、os.popen().read()等函數來執行命令行指令的,另外還有一個很少使用的commands模塊。但是從Python 2.4開始官方文檔中建議使用的是subprocess模塊。
?
subprocess是Python 2.4中新增的一個模塊,它允許你生成新的進程,連接到它們的 input/output/error 管道,并獲取它們的返回(狀態)碼。這個模塊的目的在于替換幾個舊的模塊和方法,如:
-
os.system
-
os.spawn*
?
?
1. subprocess模塊中的常用函數
| subprocess.run() | Python 3.5中新增的函數。執行指定的命令,等待命令執行完成后返回一個包含執行結果的CompletedProcess類的實例。 |
| subprocess.call() | 執行指定的命令,返回命令執行狀態,其功能類似于os.system(cmd)。 |
| subprocess.check_call() | Python 2.5中新增的函數。 執行指定的命令,如果執行成功則返回狀態碼,否則拋出異常。其功能等價于subprocess.run(..., check=True)。 |
| subprocess.check_output() | Python 2.7中新增的的函數。執行指定的命令,如果執行狀態碼為0則返回命令執行結果,否則拋出異常。 |
| subprocess.getoutput(cmd) | 接收字符串格式的命令,執行命令并返回執行結果,其功能類似于os.popen(cmd).read()和commands.getoutput(cmd)。 |
| subprocess.getstatusoutput(cmd) | 執行cmd命令,返回一個元組(命令執行狀態, 命令執行結果輸出),其功能類似于commands.getstatusoutput()。 |
說明:
在Python 3.5之后的版本中,官方文檔中提倡通過subprocess.run()函數替代其他函數來使用subproccess模塊的功能;
在Python 3.5之前的版本中,我們可以通過subprocess.call(),subprocess.getoutput()等上面列出的其他函數來使用subprocess模塊的功能;
subprocess.run()、subprocess.call()、subprocess.check_call()和subprocess.check_output()都是通過對subprocess.Popen的封裝來實現的高級函數,因此如果我們需要更復雜功能時,可以通過subprocess.Popen來完成。
subprocess.getoutput()和subprocess.getstatusoutput()函數是來自Python 2.x的commands模塊的兩個遺留函數。它們隱式的調用系統shell,并且不保證其他函數所具有的安全性和異常處理的一致性。另外,它們從Python 3.3.4開始才支持Windows平臺。
?
?
?
2. 上面各函數的定義及參數說明
函數參數列表:
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, universal_newlines=False)?subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)?subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)?subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)?subprocess.getstatusoutput(cmd)?subprocess.getoutput(cmd)
?
參數說明:
-
args: 要執行的shell命令,默認應該是一個字符串序列,該參數用于啟動進程。這可能是一個列表或一個字符串。如['df', '-Th']或('df', '-Th')【“'df -Th”這里是linux命令,在終端里輸入的,功能是顯示磁盤分區相關信息】,也可以是一個字符串,如'df -Th',但是此時需要把shell參數的值置為True。
-
shell: 如果shell為True,那么指定的命令將通過shell執行。如果我們需要訪問某些shell的特性,如管道、文件名通配符、環境變量擴展功能,這將是非常有用的。當然,python本身也提供了許多類似shell的特性的實現,如glob、fnmatch、os.walk()、os.path.expandvars()、os.expanduser()和shutil等。當shell=True時,表示在系統默認的shell環境中執行新的進程,此shell在windows表示為cmd.exe,在linux為/bin/sh。shell=True如果與不可信輸入結合使用,傳遞可能會帶來安全隱患。執行輸入的命令最好不要設置為True,以防shell注入。
-
check: 如果check參數的值是True,且執行命令的進程以非0狀態碼退出,則會拋出一個CalledProcessError的異常,且該異常對象會包含 參數、退出狀態碼、以及stdout和stderr(如果它們有被捕獲的話)。
-
參數stdin, stdout, stderr分別表示程序的標準輸入、輸出、錯誤句柄。他們可以是PIPE,文件描述符或文件對象,也可以設置為None,表示從父進程繼承。PIPE表示創建管道。stderr特殊,可以設置成STDOUT,表示與標準輸出一致
-
run()函數默認不會捕獲命令執行結果的正常輸出和錯誤輸出,如果我們向獲取這些內容需要傳遞subprocess.PIPE,然后可以通過返回的CompletedProcess類實例的stdout和stderr屬性或捕獲相應的內容;
-
call()和check_call()函數返回的是命令執行的狀態碼,而不是CompletedProcess類實例,所以對于它們而言,stdout和stderr不適合賦值為subprocess.PIPE;
-
check_output()函數默認就會返回命令執行結果,所以不用設置stdout的值,如果我們希望在結果中捕獲錯誤信息,可以執行stderr=subprocess.STDOUT。
-
input: 該參數是傳遞給Popen.communicate(),通常該參數的值必須是一個字節序列,如果universal_newlines=True,則其值應該是一個字符串。
-
universal_newlines: 該參數影響的是輸入與輸出的數據格式,比如它的值默認為False,此時stdout和stderr的輸出是字節序列;當該參數的值設置為True時,stdout和stderr的輸出是字符串。
?
args 所有調用的必填參數,參數值為字符串、序列。處于方便,通常更偏向于提供序列。如果傳遞的是單一字符串,shell參數值必須為True,如果不提供其它任何參數,傳遞單一字符串的情況下,該字符串必須是需要執行的程序名。
?
?
subprocess.PIPE
subprocess.PIPE是特殊值,可用作標準輸入,標準輸出或標準錯誤參數,stdout 負責接收正常的輸出,stderr 負責接收錯誤輸出,subprocess.PIPE 負責處理整體的數據流,把錯誤信息通過管道賦值給變量stderr,把輸出信息通過管道賦值給stdout.
?
3、subprocess.run()
subprocess.run()函數是Python3.5中新增的一個高級函數,功能是創建子進程執行某個命令,其返回值是一個subprocess.CompletedPorcess類的實例。
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False)
run()函數默認不會捕獲命令執行結果的正常輸出和錯誤輸出,如果我們向獲取這些內容需要傳遞subprocess.PIPE,然后可以通過返回的CompletedProcess類實例的stdout和stderr屬性或捕獲相應的內容。
?
subprocess.CompletedProcess類介紹
subprocess.CompletedProcess表示的是一個已結束進程的狀態信息,它所包含的屬性如下:
-
args: 用于加載該進程的參數,這可能是一個列表或一個字符串
-
returncode: 子進程的退出狀態碼。通常情況下,退出狀態碼為0則表示進程成功運行了;一個負值-N表示這個子進程被信號N終止了
-
stdout: 從子進程捕獲的stdout。這通常是一個字節序列,如果run()函數被調用時指定universal_newlines=True,則該屬性值是一個字符串。如果run()函數被調用時指定stderr=subprocess.STDOUT,那么stdout和stderr將會被整合到這一個屬性中,且stderr將會為None
-
stderr: 從子進程捕獲的stderr。它的值與stdout一樣,是一個字節序列或一個字符串。如果stderr滅有被捕獲的話,它的值就為None
-
check_returncode(): 如果returncode是一個非0值,則該方法會拋出一個CalledProcessError異常。
?
subprocess.run()例子
?
例子1(測試環境 Windows7 )
import subprocess?v1 = subprocess.run(["ping","www.baidu.com"],stderr = subprocess.PIPE,stdout=subprocess.PIPE,universal_newlines=True)# 執行ping命令,niversal_newlines為True時,stdout將輸出字符串v2 = subprocess.run("calc",stderr = subprocess.PIPE,stdout=subprocess.PIPE) #打開系統自帶的計算器print(v1.stdout)
輸出結果
?
正在 Ping www.a.shifen.com [119.75.213.61] 具有 32 字節的數據:來自 119.75.213.61 的回復: 字節=32 時間=44ms TTL=52來自 119.75.213.61 的回復: 字節=32 時間=43ms TTL=52來自 119.75.213.61 的回復: 字節=32 時間=43ms TTL=52來自 119.75.213.61 的回復: 字節=32 時間=44ms TTL=52?119.75.213.61 的 Ping 統計信息:數據包: 已發送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),往返行程的估計時間(以毫秒為單位):最短 = 43ms,最長 = 44ms,平均 = 43ms
??
?
?
例子2(測試環境 ubuntu16.04.1)
import subprocess?p = subprocess.run(["ls","-l"],stderr=subprocess.PIPE,stdout=subprocess.PIPE,universal_newlines=True)print(p.stdout)
輸出結果
?
?
?
?
?
4、subprocess.call()
subprocess.call運行args描述的命令,等待命令完成后返回returncode屬性。subprocess.call返回的是命令執行的狀態碼,而不是CompletedProcess類實例,所以對于它們而言,stdout和stderr不適合賦值subprocess.PIPE。
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
?
-
returncode: 子進程的退出狀態碼。通常情況下,退出狀態碼為0則表示進程成功運行了;一個負值-N表示這個子進程被信號N終止了。
?
subprocess.call()例子
例子1(測試環境 Windows7 )
import subprocessp = subprocess.call(["calc"])print(p)
輸出結果
0
?
?
例子2(測試環境 ubuntu16.04.1)
?
import subprocess?p = subprocess.call("ls -l",shell=True)print("----------------------")print(p)
?
輸出結果
總用量 16drwxrwxr-x 3 nicholas nicholas 4096 5月 23 00:23 b-rw-rw-r-- 1 nicholas nicholas 0 5月 8 19:03 __init__.py-rwxrw-r-- 1 nicholas nicholas 16 5月 22 21:58 old_test.txtdrwxrwxr-x 3 nicholas nicholas 4096 5月 23 00:23 temp-rw-rw-r-- 1 nicholas nicholas 100 5月 30 20:02 test.py----------------------0
?
?
?
?
5、subprocess.Popen介紹
subprocess.Popen類用于在一個新的進程中執行一個子程序,用來創建子進程。
上面的subprocess.run()、subprocess.call()等介紹的這些函數都是基于subprocess.Popen類實現的,通過使用這些被封裝后的高級函數可以很方面的完成一些常見的需求。由于subprocess模塊底層的進程創建和管理是由Popen類來處理的。
(1)subprocess.Popen的構造函數
class subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False,startup_info=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=())
?
參數說明:
-
args: 要執行的shell命令,可以是字符串,也可以是命令各個參數組成的序列。當該參數的值是一個字符串時,該命令的解釋過程是與平臺相關的,因此通常建議將args參數作為一個序列傳遞。
-
bufsize: 指定緩存策略,0表示不緩沖,1表示行緩沖,其他大于1的數字表示緩沖區大小,負數 表示使用系統默認緩沖策略。
-
stdin, stdout, stderr: 分別表示程序標準輸入、輸出、錯誤句柄。合法值為PIPE,DEVNULL,已存在文件描述符(一個正整數),已存在文件對象和None。 PIPE表示應該創建通往子進程的管道。DEVNULL表示應該使用指定文件os.devnull。默認參數None則表示無進行重定向,子進程文件句柄從父進程繼承。此外,stderr還可以是STDOUT,表明子進程的錯誤數據應該被放進相同的文件句柄stdout。
-
preexec_fn: 用于指定一個將在子進程運行之前被調用的可執行對象,只在Unix平臺下有效。
-
close_fds: 如果該參數的值為True,則除了0,1和2之外的所有文件描述符都將會在子進程執行之前被關閉。
默認值根據平臺而異。Unix平臺總是默認為True。在windows平臺下,如果close_fds被設置為True,則新創建的子進程將不會繼承父進程的輸入、輸出、錯誤管道。(標準的輸入,輸出和錯誤輸出分別表示為STDIN,STDOUT,STDERR,也可以用0,1,2來表示。)
-
shell: 該參數用于標識是否使用shell作為要執行的程序,如果shell值為True,則建議將args參數作為一個字符串傳遞而不要作為一個序列傳遞。為True時,表示將通過shell來執行。
-
cwd: 如果該參數值不是None,則該函數將會在執行這個子進程之前改變當前工作目錄到cwd。
-
env: 用于指定子進程的環境變量,如果env=None,那么子進程的環境變量將從父進程中繼承。如果env!=None,它的值必須是一個映射對象。
-
universal_newlines: 如果該參數值為True,則該文件對象的stdin,stdout和stderr將會作為文本流被打開,否則他們將會被作為二進制流被打開。
-
startupinfo和creationflags: 這兩個參數只在Windows下有效,它們將被傳遞給底層的CreateProcess()函數,用于設置子進程的一些屬性,如主窗口的外觀,進程優先級等。
?
(2) subprocess.Popen類的實例可調用的方法
| Popen.poll() | 用于檢查子進程(命令)是否已經執行結束,沒結束返回None,結束后返回狀態碼。 |
| Popen.wait(timeout=None) | 等待子進程結束,并返回狀態碼;如果在timeout指定的秒數之后進程還沒有結束,將會拋出一個TimeoutExpired異常。 |
| Popen.communicate(input=None, timeout=None) | 該方法可用來與進程進行交互,比如發送數據到stdin,從stdout和stderr讀取數據,直到到達文件末尾。 |
| Popen.send_signal(signal) | 發送指定的信號給這個子進程。 |
| Popen.terminate() | 停止該子進程。 |
| Popen.kill() | 殺死該子進程。 |
?
關于communicate()方法的說明:
-
該方法中的可選參數 input 應該是將被發送給子進程的數據,或者如沒有數據發送給子進程,該參數應該是None。input參數的數據類型必須是字節串,如果universal_newlines參數值為True,則input參數的數據類型必須是字符串。
-
該方法返回一個元組(stdout_data, stderr_data),這些數據將會是字節穿或字符串(如果universal_newlines的值為True)。
-
如果在timeout指定的秒數后該進程還沒有結束,將會拋出一個TimeoutExpired異常。捕獲這個異常,然后重新嘗試通信不會丟失任何輸出的數據。但是超時之后子進程并沒有被殺死,為了合理的清除相應的內容,一個好的應用應該手動殺死這個子進程來結束通信。
-
需要注意的是,這里讀取的數據是緩沖在內存中的,所以,如果數據大小非常大或者是無限的,就不應該使用這個方法。
?
例子1(測試環境 ubuntu16.04.1)
import subprocess?# 創建子進程,執行“ls -l”,查看當前目錄的文件具體信息p = subprocess.Popen("ls -l",stdout=subprocess.PIPE,shell=True,universal_newlines=True)#p是Popen的一個實例對象 print(p.stdout.read()) #調用Popen的stdout,read()方法讀取輸出結果?# 修改執行的目錄到/temp,然后再創建子進程,執行“ls -l”,查看當前目錄文件的具體信息p1 = subprocess.Popen("ls -l",stdout=subprocess.PIPE,shell=True,universal_newlines=True,cwd='/tmp')print(p1.stdout.read())
?
輸出結果
?
?
?
?
例子2(測試環境 ubuntu16.04.1)
import subprocess?p = subprocess.Popen(["python3"],shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) #實例化suprocess.Popen類創建子進程p對象p.stdin.write("print('nicholas 1') \n")p.stdin.write("print('hello 2') \n")p.stdin.write("print('hworld 3') \n")out,err = p.communicate()print(out)
輸出結果
nicholas 1hello 2hworld 3
?
分析執行過程
?
1、實例化suprocess.Popen類創建子進程P對象,調用shell終端,在終端里執行"python3"命令,終端進入python環境,
2、向子進程輸入"print('nicholas 1') \n"語句,向子進程輸入"print('hello 2') \n"語句,向子進程輸入"print('hworld 3') \n"語句
3、通過調用對象p的communicate()方法,獲取子進程的輸出、錯誤
4、打印子進程的輸出
5、注意參數universal_newlines是True時,stdin.write要傳入字符串,參數是False時要傳入bytes類型。
?
例子3(測試環境 Windows7 )
import subprocess?p = subprocess.Popen(["python"],shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True)out,err = p.communicate(input="print('nicholas niubi')")print(out)
輸出結果
nicholas niubi
?
?
分析:
1、實例化suprocess.Popen類創建子進程p對象,打開Windows命令行,執行“python”命令,進入python環境
2、通過調用對象p的communicate()方法向stdin發送數據"print('nicholas niubi')",并分別獲取對象p的輸出和錯誤
3、打印輸出
4、注意參數universal_newlines是True時,input要傳入字符串,參數是False時要傳入bytes類型。
?
?
?
?
參考資料
[1]https://www.cnblogs.com/Eva-J/articles/8253521.html
[2]https://www.cnblogs.com/Eva-J/articles/8253549.html
[3]https://www.cnblogs.com/yyds/p/7288916.html
轉載于:https://www.cnblogs.com/Nicholas0707/p/9114601.html
總結
以上是生活随笔為你收集整理的Python之路(第二十篇) subprocess模块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 撞出租车误工费保险公司赔吗
- 下一篇: 2018-06-25-Python全栈开