1. 程式人生 > >python子程序模組subprocess詳解與應用例項 一

python子程序模組subprocess詳解與應用例項 一

一、subprocess 模組簡介
subprocess最早是在2.4版本中引入的。
subprocess模組用來生成子程序,並可以通過管道連線它們的輸入/輸出/錯誤,以及獲得它們的返回值。
它用來代替多箇舊模組和函式:
os.system
os.spawn*
os.popen*
popen2.*
commands.*

關於這個模組可以取代的舊函式可以參見 subprocess-replacements 一節。
POSIX使用者(Linux, BSD, etc)還可以安裝和使用更新的subprocess32模組來代替python 2.7版本中的subprocess.
subprocess32雖然是一個低版本,但在有些情況下效果更好。
1.1. 使用 subprocess模組
啟動子程序的推薦方式是使用下面的便利功能。
當這些還不能滿足需求時,就需要使用底層的Popen介面。
1. subprocess.call
語法:
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)
語義:
執行由args指定的命令,直到命令結束後,返回 返回碼的屬性值。

上面的引數是最常見的方式,下面是示例程式碼:

>

subprocess.call([“ls”, “-l”])
0
subprocess.call(“exit 1”, shell=True)
1
WARNING: 使用 shell=True 是一種安全保護機制。
NOTE: 在使用這個函式時,不要使用 stdout=PIPE 或 stderr=PIPE 引數,
不然會導致子程序輸出的死鎖。
如果要使用管道,可以在 communicate()方法中使用Popen
示例程式碼:
import subprocess
rc = subprocess.call([“ls”,”-l”])

可以通過一個shell來解釋一整個字串:
import subprocess
out = subprocess.call(“ls -l”, shell=True)
out = subprocess.call(“cd ..”, shell=True)

使用了shell=True這個引數。
這個時候,我們使用一整個字串,而不是一個表來執行子程序。
Python將先執行一個shell,再用這個shell來解釋這整個字串。

shell命令中有一些是shell的內建命令,這些命令必須通過shell執行,$cd。
shell=True允許我們執行這樣一些命令。
2. subprocess.check_call
語法:
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False)
語義:
執行由args指定的命令,直到命令執行完成。
如果返回碼為零,則返回。否則,丟擲 CalledProcessError異常。
CalledProcessError物件包含有返回碼的屬性值。

上面顯示的引數僅僅是最常見的,下面是使用者更常用的引數。
示例程式碼如下:

>

subprocess.check_call([“ls”, “-l”])
0
subprocess.check_call(“exit 1”, shell=True)
Traceback (most recent call last):

subprocess.CalledProcessError: Command ‘exit 1’ returned non-zero exit status 1

這個函式在python 2.5版本中引入。
WARNING: 使用 shell=True 是一種安全機制。
NOTE: 不要在這個函式中使用 stdout=PIPE 或 stderr=PIPE, 否則會造成子程序死鎖。
如果需要使用管道,可以在 communicate()方法中使用Popen.
3. subprocess.check_output
語法:
subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)
語義:
執行args定義的命令,並返回一個字串表示的輸出值。
如果返回碼為非零,則丟擲 CalledProcessError異常。
示例程式碼:

>

subprocess.check_output([“echo”, “Hello World!”])
‘Hello World!\n’
subprocess.check_output(“exit 1”, shell=True)
Traceback (most recent call last):

subprocess.CalledProcessError: Command ‘exit 1’ returned non-zero exit status 1
如果要捕捉結果中的標準錯誤,使用 stderr=subprocess.STDOUT引數:

subprocess.check_output(
… “ls non_existent_file; exit 0”,
… stderr=subprocess.STDOUT,
… shell=True)
‘ls: non_existent_file: No such file or directory\n’
這個函式在python 2.7版本中引入。
WARNING: 使用 shell=True 是一種安全機制。
NOTE: 不要在這個函式中使用 stdout=PIPE 或 stderr=PIPE, 否則會造成子程序死鎖。
如果需要使用管道,可以在 communicate()方法中使用Popen.
4. subprocess.PIPE
使用Popen時,用於 stdin, stdout和stderr引數的特殊值,表示開啟連線標準流的管道。
5. subprocess.STDOUT
使用Popen時,用於 stderr 引數的特殊值,表示將標準錯誤重定向到標準輸出的同一個控制代碼。
6. 異常 subprocess.CalledProcessError
當由 check_call()或 check_output()執行的程序返回非零狀態值時丟擲的異常。
7. returncode
子程序的退出狀態。
8. cmd
子程序執行的命令。
9. output
如果check_output()丟擲異常時,子程序的輸出值。
否則,沒有這個值。
1.1.1. 常用的引數
為了支援各種使用者使用情況 ,Popen構建函式接收多種可選引數。
對於最典型的情況,許多引數都保留有安全的預設值,這些最常用的方式如下:
1. args
所有的函式都需要這個引數,並且它是一個字串,或者是程式的引數序列。
提供一個引數序列是更推薦的方式,因為這樣能允許模組接收空格 或 引號中的引數。
如果傳遞的是單個字串,要麼 shell=True, 或都要麼 字串就程式名字,並且不能帶引數。
2. stdin, stdout 和 stderr
stdin, stdout和stderr指定了執行程式的標準輸入,標準輸出和標準錯誤的檔案控制代碼。
它們的值可以是PIPE, 一個存在的檔案描述符(正整數),一個存在的檔案物件,或 None.
PIPE 表示建立一個連線子程序的新管道。
預設值 為 None, 表示不做重定向。
子程序的檔案控制代碼可以從父程序中繼承得到。
另外,stderr可以設定值為 STDOUT,表示子程序的錯誤資料可以和標準輸出是同一個檔案控制代碼。

當stdout 或 stderr的值為管道 並且 universal_newlines的值為真時,
對於以 ‘U’模式引數開啟的新行,所有行的結束都會轉換成’\n’。
3. shell
如果 shell的值為 True, 則指定的命令列會通過shell來執行。
如果你使用Python來作為流程控制,那這樣的設定會很有用,因為它提供了絕大多數的系統shell命令且可以很方便地使用
shell的各種功能,如 shell 管道,檔名萬用字元,環境變數擴充套件,以及使用者目錄擴充套件符 ~。
但是,需要注意的是,Python 提供了類似shell功能的實現。

WARNING: 執行不受信任來源的shell命令會是一個嚴重的安全問題。
基於這一點,shell=True 是不建議的。
示例程式碼如下:

>

from subprocess import call
filename = input(“What file would you like to display?\n”)
What file would you like to display?
non_existent; rm -rf / #
call(“cat ” + filename, shell=True) # Uh-oh. This will end badly…

shell=False 關閉了shell的所有基本功能 ,從而不會有上面所說的安全漏洞。
可以在Popen構建函式的幫助文件中看到,它只有在 shell=False時才能工作。
當使用 shell=True時,pipes.quote()可以被用於轉譯空格,shell的字元等。
1.1.2. Popen構建函式
subprocess中更底層的程序建立和管理可以通過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)
語義:
在新程序中執行一個子程式。
在Unix中,這個類使用 類似於 os.execvp()方式來執行子程式。
在Windows中,這個類使用Windows的 CreateProcess()函式來執行子程式。
引數解析:
args: 一個程式引數序列,或者單個字串。
預設的,要執行的程式應該是序列的第一個欄位。
如果單個字串,它的解析依賴於平臺
在Unix中,如果 args是一個字串,那麼這個字串解釋成被執行程式的名字或路徑。
然而,這種情況只能用在不需要引數的程式。

NOTE: 當對args確定了正確的分隔符後,shlex.split()就很有用,特別是在複雜的情況下:

>

import shlex, subprocess
command_line = raw_input()
/bin/vikings -input eggs.txt -output “spam spam.txt” -cmd “echo ‘MONEYargs=shlex.split(commandline)printargs[/bin/vikings,input,eggs.txt,output,spamspam.txt,cmd,echoMONEY’”]
p = subprocess.Popen(args) # Success!

NOTE: 選項(如 -input) 和 引數(如 eggs.txt) 在shell中是用空格分隔成分離的列表元素。
如果引數需要引號或反斜線,則它們會是一個單一列表元素。
shell引數(預設值為False)聲明瞭是否使用shell來執行程式。
如果 shell=True, 它將args看作是一個字串,而不是一個序列。

在Unix系統,且 shell=True時,shell預設使用 /bin/sh.
如果 args是一個字串,則它聲明瞭通過shell執行的命令。這意味著,字串必須要使用正確的格式。
如果 args是一個序列,則第一個元素就是命令字串,而其它的元素都作為引數使用。
可以這樣說,Popen等價於:
Popen([‘/bin/sh’, ‘-c’, args[0], args[1], …])
bufsize: 如果指定了值,則它和內建函式 open()對應的引數有相同的意義:
0 – 表示不緩衝
1 – 表示緩衝
任何其它的正數值表示buffer的大小。
負數值表示使用系統預設值,通常表示完全緩衝。
它的預設值為零。
NOTE: 如果遇到效能問題,建議將bufsize設定成 -1 或足夠大的正數(如 4096)。

executable: 指定了用於代替執行的程式。它極少會用到。
stdin, stdout, stderr:指定了執行程式的標準輸入,標準輸出和標準錯誤的檔案控制代碼。
有效的值可以是 PIPE, 一個存在的檔案描述符,或存在的檔案物件,或 None.
預設值為 None。
stderr可以設定成STDOUT, 它表示將子程序的stderr資料重定向到stdout.
preexec_fn: 如果它被設定成可呼叫物件,那麼這個物件會在子程序執行前被子程序呼叫,只用於Unix.
close_fds: 如果設定為True, 則在子程序被執行前,除0,1和2之外的所有檔案描述符都將被關閉,只用於Unix。
cwd: 當它不為 None時,子程式在執行前,它的當前路徑會被替換成 cwd的值。
這個路徑並不會被新增到可執行程式的搜尋路徑,所以cwd不能是相對路徑。
env: 當它不為 None時,它是新程序的環境變數的對映。
可以用它來代替當前程序的環境。
universal_newlines: 為真時,檔案物件 stdout和 stderr都被以文字檔案的方式開啟

示例程式碼:
1. Popen物件建立後,主程式不會自動等待子程序完成。
我們必須呼叫物件的wait()方法,父程序才會等待 (也就是阻塞block):
import subprocess
child = subprocess.Popen([“ping”,”-c”,”5”,”www.google.com”])
print(“parent process”)

從執行結果中看到,父程序在開啟子程序之後並沒有等待child的完成,而是直接執行print。

  1. 對比等待的情況:
    import subprocess
    child = subprocess.Popen([“ping”,”-c”,”5”,”www.google.com”])
    child.wait()
    print(“parent process”)

此外,你還可以在父程序中對子程序進行其它操作,比如我們上面例子中的child物件:
child.poll() # 檢查子程序狀態
child.kill() # 終止子程序
child.send_signal() # 向子程序傳送訊號
child.terminate() # 終止子程序
子程序的PID儲存在child.pid

  1. 可以在Popen()建立子程序的時候改變標準輸入、標準輸出和標準錯誤,
    並可以利用subprocess.PIPE將多個子程序的輸入和輸出連線在一起,構成管道(pipe):
    import subprocess
    child1 = subprocess.Popen([“ls”,”-l”], stdout=subprocess.PIPE)
    child2 = subprocess.Popen([“wc”], stdin=child1.stdout,stdout=subprocess.PIPE)
    out = child2.communicate()
    print(out)

subprocess.PIPE實際上為文字流提供一個快取區。
child1的stdout將文字輸出到快取區,隨後child2的stdin從該PIPE中將文字讀取走。
child2的輸出文字也被存放在PIPE中,直到communicate()方法從PIPE中讀取出PIPE中的文字。
要注意的是,communicate()是Popen物件的一個方法,該方法會阻塞父程序,直到子程序完成。

  1. 還可以利用communicate()方法來使用PIPE給子程序輸入:
    import subprocess
    child = subprocess.Popen([“cat”], stdin=subprocess.PIPE)
    child.communicate(“vamei”)
    我們啟動子程序之後,cat會等待輸入,直到我們用communicate()輸入”vamei”。

通過使用subprocess包,我們可以執行外部程式。這極大的拓展了Python的功能。
如果你已經瞭解了作業系統的某些應用,你可以從Python中直接呼叫該應用(而不是完全依賴Python),
並將應用的結果輸出給Python,並讓Python繼續處理。
shell的功能(比如利用文字流連線各個應用),就可以在Python中實現。
1.1.3.異常
在開始執行新程式之前,子程序丟擲的異常,會被重新丟擲到父程序。
另外,異常物件會有一個額外的屬性,叫做 child_traceback, 它是一個字串,包含從子程式的觀察點追蹤到的資訊。

最常見的丟擲的異常是 OSError, 當它發生時,通常是我們執行了一個不存在的檔案。應用程式應當要能處理這個異常。
如果使用無效的引數呼叫 Popen,會丟擲 ValueError異常。
如果被呼叫程序的返回碼不為零,則check_call()和check_output()會丟擲 CalledProcessError異常。
1.1.4. 安全
Unlike some other popen functions, this implementation will never call a system shell implicitly.
This means that all characters, including shell metacharacters, can safely be passed to child processes.
Obviously, if the shell is invoked explicitly, then it is the application’s responsibility to ensure that
all whitespace and metacharacters are quoted appropriately.