subprocess介紹
需要用到Python來執行shell腳本, 因此需要查看下subprocess模塊文檔。
根據官網文檔描述:subprocess模塊用於創建子進程, 這個模塊用於替換舊版本中的一些模塊, 如:os.system,
os.spawn*, os.popen*, os.popen*, popen2.*, commands.*, subprocess允許你能創建很多子進程, 創建的時候能能指定子進程和子進程的輸入、輸出、錯誤輸出管道, 執行后能獲取輸出結果和執行狀態。
subprocess模塊的常用方法用法介紹
subprocess.run() --> python3.5中新增的函數, 執行指定的命令, 等待命令執行完成后返回一個包含執行結果的 CompletedProcess類的實例。
subprocess.call(): --> 執行指定的命令, 返回命令執行狀態, 功能類似羽os.system(cmd)
subprocess.check_call(): --> python2.5中新增的函數, 執行指定的命令, 如果執行成功則返回狀態碼, 否則拋出異常。
【Tips】: 在python3.5之后的版本中, 官方文檔中提倡通過subprocess.run()函數替代其他函數來使用subprocess模塊的功能。在python3.5之前的版本中, 我們可以通過subprocess.call()來使用subprocess模塊的功能。subprocess.run(), subprocess.call(), subprocess.check_call()都是通過對subprocess.Popen的封裝來實現的高級函數。
###
三. 這幾個函數的定義以及參數
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)
參數說明:
args: 要執行的shell命令, 默認應該是一個字符串序列, 如['ls', '-l'], 也可以是一個字符串如: 'ls -l', 但是此時需要把shell參數的值置為True。
【Tips】--> shell=True參數會讓subprocess.call接受字符串類型的變量作為命令, 並調用shell去執行這個字符串, 當shell=False時, subprocess.call只接受數組變量作為命令, 並將數組的第一個元素作為命令, 剩下的全部作為該命令的參數。官方不推薦使用shell=True。
p = subprocess.run(["pwd"]) # p1 = subprocess.run(["cd"]) # p2 = subprocess.run(["ls", "-l"]) print(p)
###
###
subprocess.Popen
subprocess的目的就是啟動一個新的進程並且與之通信。
subprocess模塊中只定義了一個類: Popen。可以使用Popen來創建進程,並與進程進行復雜的交互。它的構造函數如下:
class 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)
###
參數args
參數args可以是字符串或者序列類型(如:list,元組),用於指定進程的可執行文件及其參數。如果是序列類型,第一個元素通常是可執行文件的路徑。我們也可以顯式的使用executeable參數來指定可執行文件的路徑。
subprocess.Popen(["cat","test.txt"])
subprocess.Popen("cat test.txt")
這兩個之中,后者將不會工作。因為如果是一個字符串的話,必須是程序的路徑才可以。(考慮unix的api函數exec,接受的是字符串
列表)
但是下面的可以工作
subprocess.Popen("cat test.txt", shell=True)
這是因為它相當於
subprocess.Popen(["/bin/sh", "-c", "cat test.txt"])
在*nix下,當shell=False(默認)時,Popen使用os.execvp()來執行子程序。args一般要是一個【列表】。如果args是個字符串的
話,會被當做是可執行文件的路徑,這樣就不能傳入任何參數了。
注意:
shlex.split()可以被用於序列化復雜的命令參數,比如:
>>> shlex.split('ls ps top grep pkill') ['ls', 'ps', 'top', 'grep', 'pkill'] >>>import shlex, subprocess >>>command_line = raw_input() /bin/cat -input test.txt -output "diege.txt" -cmd "echo '$MONEY'" >>>args = shlex.split(command_line) >>> print args ['/bin/cat', '-input', 'test.txt', '-output', 'diege.txt', '-cmd', "echo '$MONEY'"] >>>p=subprocess.Popen(args)
###
可以看到,空格分隔的選項(如-input)和參數(如test.txt)會被分割為列表里獨立的項,但引號里的或者轉義過的空格不在此列
。這也有點像大多數shell的行為。
在*nix下,當shell=True時,如果arg是個字符串,就使用shell來解釋執行這個字符串。如果args是個列表,則第一項被視為命令,
其余的都視為是給shell本身的參數。也就是說,等效於:
subprocess.Popen(['/bin/sh', '-c', args[0], args[1], ...])
####
參數bufsize
參數bufsize一般0 無緩沖,1 行緩沖,其他正值 緩沖區大小,負值 采用默認系統緩沖(一般是全緩沖)
###
參數executable
executable一般不用,args字符串或列表第一項表示程序名
###
參數stdin, stdout, stderr
參數stdin, stdout, stderr分別表示程序的標准輸入、輸出、錯誤句柄。他們可以是PIPE,文件描述符或文件對象,也可以設置為None,表示從父進程繼承。
subprocess.PIPE
在創建Popen對象時,subprocess.PIPE可以初始化stdin, stdout或stderr參數。表示與子進程通信的標准流。
subprocess.STDOUT
創建Popen對象時,用於初始化stderr參數,表示將錯誤通過標准輸出流輸出。
###
參數shell
如果參數shell設為true,程序將通過shell來執行。
####
參數env
參數env是字典類型,用於指定子進程的環境變量。如果env = None,子進程的環境變量將從父進程中繼承。
###
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。
Popen.send_signal(signal)
向子進程發送信號。
Popen.terminate()
停止(stop)子進程。在windows平台下,該方法將調用Windows API TerminateProcess()來結束子進程。
Popen.kill()
殺死子進程。
Popen.pid
獲取子進程的進程ID。
Popen.returncode
獲取進程的返回值。如果進程還沒有結束,返回None。
###
進程通信:
如果想得到進程的輸出,管道是個很方便的方法:
p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdoutput,erroutput) = p.communicate()
###
import subprocess p = subprocess.Popen("ls", shell=True, stdout=subprocess.PIPE) (stdoutput, erroutput) = p.communicate() print(stdoutput) print(erroutput)
####
p.communicate會一直等到進程退出,並將標准輸出和標准錯誤輸出返回,這樣就可以得到子進程的輸出了。
上面的例子通過communicate給stdin發送數據,然后使用一個tuple接收命令的執行結果。
上面,標准輸出和標准錯誤輸出是分開的,也可以合並起來,只需要將stderr參數設置為subprocess.STDOUT就可以了,這樣子:
p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (stdoutput,erroutput) = p.communicate()
如果你想一行行處理子進程的輸出,也沒有問題:
p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: buff = p.stdout.readline() if buff == '' and p.poll() != None: break
死鎖
但是如果你使用了管道,而又不去處理管道的輸出,那么小心點,如果子進程輸出數據過多,死鎖就會發生了,比如下面的用法:
p=subprocess.Popen("longprint", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p.wait()
longprint是一個假想的有大量輸出的進程,當輸出達到4096時,死鎖就發生了。當然,如果我們用p.stdout.readline或者p.communicate去清理輸出,那么無論輸出多少,死鎖都是不會發生的。或者我們不使用管道,比如不做重定向,或者重定向到文件,也都是可以避免死鎖的。
subprocess還可以連接起來多個命令來執行。
在shell中我們知道,想要連接多個命令可以使用管道。
在subprocess中,可以使用上一個命令執行的輸出結果作為下一次執行的輸入。例子如下: