subprocess--子進(jìn)程管理器一、subprocess 模塊簡介subprocess最早是在2.4版本中引入的。subprocess模塊用來生成子進(jìn)程,并可以通過管道連接它們的輸入/輸出/錯誤,以及獲得它們的返回值。 它用來代替多個舊模塊和函數(shù): os.system os.spawn* os.popen* popen2.* commands.* 關(guān)于這個模塊可以取代的舊函數(shù)可以參見 subprocess-replacements 一節(jié)。 POSIX用戶(Linux, BSD, etc)還可以安裝和使用更新的subprocess32模塊來代替python 2.7版本中的subprocess. subprocess32雖然是一個低版本,但在有些情況下效果更好。 1.1. 使用 subprocess模塊啟動子進(jìn)程的推薦方式是使用下面的便利功能。當(dāng)這些還不能滿足需求時,就需要使用底層的Popen接口。 1. subprocess.call語法:subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False) 語義: 運(yùn)行由args指定的命令,直到命令結(jié)束后,返回 返回碼的屬性值。 上面的參數(shù)是最常見的方式,下面是示例代碼: >>> >>> subprocess.call(["ls", "-l"]) 0 >>> subprocess.call("exit 1", shell=True) 1 WARNING: 使用 shell=True 是一種安全保護(hù)機(jī)制。 NOTE: 在使用這個函數(shù)時,不要使用 stdout=PIPE 或 stderr=PIPE 參數(shù), 不然會導(dǎo)致子進(jìn)程輸出的死鎖。 如果要使用管道,可以在 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這個參數(shù)。 這個時候,我們使用一整個字符串,而不是一個表來運(yùn)行子進(jìn)程。 Python將先運(yùn)行一個shell,再用這個shell來解釋這整個字符串。 shell命令中有一些是shell的內(nèi)建命令,這些命令必須通過shell運(yùn)行,$cd。 shell=True允許我們運(yùn)行這樣一些命令。 2. subprocess.check_call語法:subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False) 語義: 運(yùn)行由args指定的命令,直到命令執(zhí)行完成。 如果返回碼為零,則返回。否則,拋出 CalledProcessError異常。 CalledProcessError對象包含有返回碼的屬性值。 上面顯示的參數(shù)僅僅是最常見的,下面是用戶更常用的參數(shù)。 示例代碼如下: >>> >>> 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 這個函數(shù)在python 2.5版本中引入。 WARNING: 使用 shell=True 是一種安全機(jī)制。 NOTE: 不要在這個函數(shù)中使用 stdout=PIPE 或 stderr=PIPE, 否則會造成子進(jìn)程死鎖。 如果需要使用管道,可以在 communicate()方法中使用Popen. 3. subprocess.check_output語法:subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False) 語義: 運(yùn)行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 如果要捕捉結(jié)果中的標(biāo)準(zhǔn)錯誤,使用 stderr=subprocess.STDOUT參數(shù): >>> >>> subprocess.check_output( ... "ls non_existent_file; exit 0", ... stderr=subprocess.STDOUT, ... shell=True) 'ls: non_existent_file: No such file or directory\n' 這個函數(shù)在python 2.7版本中引入。 WARNING: 使用 shell=True 是一種安全機(jī)制。 NOTE: 不要在這個函數(shù)中使用 stdout=PIPE 或 stderr=PIPE, 否則會造成子進(jìn)程死鎖。 如果需要使用管道,可以在 communicate()方法中使用Popen. 4. subprocess.PIPE使用Popen時,用于 stdin, stdout和stderr參數(shù)的特殊值,表示打開連接標(biāo)準(zhǔn)流的管道。5. subprocess.STDOUT使用Popen時,用于 stderr 參數(shù)的特殊值,表示將標(biāo)準(zhǔn)錯誤重定向到標(biāo)準(zhǔn)輸出的同一個句柄。6. 異常 subprocess.CalledProcessError當(dāng)由 check_call()或 check_output()運(yùn)行的進(jìn)程返回非零狀態(tài)值時拋出的異常。7. returncode子進(jìn)程的退出狀態(tài)。8. cmd子進(jìn)程執(zhí)行的命令。9. output如果check_output()拋出異常時,子進(jìn)程的輸出值。否則,沒有這個值。 1.1.1. 常用的參數(shù)為了支持各種用戶使用情況 ,Popen構(gòu)建函數(shù)接收多種可選參數(shù)。對于最典型的情況,許多參數(shù)都保留有安全的默認(rèn)值,這些最常用的方式如下: 1. args所有的函數(shù)都需要這個參數(shù),并且它是一個字符串,或者是程序的參數(shù)序列。提供一個參數(shù)序列是更推薦的方式,因為這樣能允許模塊接收空格 或 引號中的參數(shù)。 如果傳遞的是單個字符串,要么 shell=True, 或都要么 字符串就程序名字,并且不能帶參數(shù)。 2. stdin, stdout 和 stderrstdin, stdout和stderr指定了執(zhí)行程序的標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤的文件句柄。它們的值可以是PIPE, 一個存在的文件描述符(正整數(shù)),一個存在的文件對象,或 None. PIPE 表示創(chuàng)建一個連接子進(jìn)程的新管道。 默認(rèn)值 為 None, 表示不做重定向。 子進(jìn)程的文件句柄可以從父進(jìn)程中繼承得到。 另外,stderr可以設(shè)置值為 STDOUT,表示子進(jìn)程的錯誤數(shù)據(jù)可以和標(biāo)準(zhǔn)輸出是同一個文件句柄。 當(dāng)stdout 或 stderr的值為管道 并且 universal_newlines的值為真時, 對于以 ‘U'模式參數(shù)打開的新行,所有行的結(jié)束都會轉(zhuǎn)換成'\n'。 3. shell如果 shell的值為 True, 則指定的命令行會通過shell來執(zhí)行。如果你使用Python來作為流程控制,那這樣的設(shè)置會很有用,因為它提供了絕大多數(shù)的系統(tǒng)shell命令且可以很方便地使用 shell的各種功能,如 shell 管道,文件名通配符,環(huán)境變量擴(kuò)展,以及用戶目錄擴(kuò)展符 ~。 但是,需要注意的是,Python 提供了類似shell功能的實現(xiàn)。 WARNING: 執(zhí)行不受信任來源的shell命令會是一個嚴(yán)重的安全問題。 基于這一點,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 關(guān)閉了shell的所有基本功能 ,從而不會有上面所說的安全漏洞。 可以在Popen構(gòu)建函數(shù)的幫助文檔中看到,它只有在 shell=False時才能工作。 當(dāng)使用 shell=True時,pipes.quote()可以被用于轉(zhuǎn)譯空格,shell的字符等。 1.1.2. Popen構(gòu)建函數(shù)subprocess中更底層的進(jìn)程創(chuàng)建和管理可以通過Popen類實現(xiàn)。它提供了更多的靈活性,程序員通過它能處理更多復(fù)雜的情況。 語法: 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) 語義: 在新進(jìn)程中執(zhí)行一個子程序。 在Unix中,這個類使用 類似于 os.execvp()方式來執(zhí)行子程序。 在Windows中,這個類使用Windows的 CreateProcess()函數(shù)來執(zhí)行子程序。 參數(shù)解析: args: 一個程序參數(shù)序列,或者單個字符串。 默認(rèn)的,要執(zhí)行的程序應(yīng)該是序列的第一個字段。 如果單個字符串,它的解析依賴于平臺 在Unix中,如果 args是一個字符串,那么這個字符串解釋成被執(zhí)行程序的名字或路徑。 然而,這種情況只能用在不需要參數(shù)的程序。 NOTE: 當(dāng)對args確定了正確的分隔符后,shlex.split()就很有用,特別是在復(fù)雜的情況下: >>> >>> import shlex, subprocess >>> command_line = raw_input() /bin/vikings -input eggs.txt -output "spam spam.txt" -cmd "echo '$MONEY'" >>> args = shlex.split(command_line) >>> print args ['/bin/vikings', '-input', 'eggs.txt', '-output', 'spam spam.txt', '-cmd', "echo '$MONEY'"] >>> p = subprocess.Popen(args) # Success! NOTE: 選項(如 -input) 和 參數(shù)(如 eggs.txt) 在shell中是用空格分隔成分離的列表元素。 如果參數(shù)需要引號或反斜線,則它們會是一個單一列表元素。 shell參數(shù)(默認(rèn)值為False)聲明了是否使用shell來執(zhí)行程序。 如果 shell=True, 它將args看作是一個字符串,而不是一個序列。 在Unix系統(tǒng),且 shell=True時,shell默認(rèn)使用 /bin/sh. 如果 args是一個字符串,則它聲明了通過shell執(zhí)行的命令。這意味著,字符串必須要使用正確的格式。 如果 args是一個序列,則第一個元素就是命令字符串,而其它的元素都作為參數(shù)使用。 可以這樣說,Popen等價于: Popen(['/bin/sh', '-c', args[0], args[1], ...]) bufsize: 如果指定了值,則它和內(nèi)建函數(shù) open()對應(yīng)的參數(shù)有相同的意義: 0 -- 表示不緩沖 1 -- 表示緩沖 任何其它的正數(shù)值表示buffer的大小。 負(fù)數(shù)值表示使用系統(tǒng)默認(rèn)值,通常表示完全緩沖。 它的默認(rèn)值為零。 NOTE: 如果遇到性能問題,建議將bufsize設(shè)置成 -1 或足夠大的正數(shù)(如 4096)。 executable: 指定了用于代替執(zhí)行的程序。它極少會用到。 stdin, stdout, stderr:指定了執(zhí)行程序的標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤的文件句柄。 有效的值可以是 PIPE, 一個存在的文件描述符,或存在的文件對象,或 None. 默認(rèn)值為 None。 stderr可以設(shè)置成STDOUT, 它表示將子進(jìn)程的stderr數(shù)據(jù)重定向到stdout. preexec_fn: 如果它被設(shè)置成可調(diào)用對象,那么這個對象會在子進(jìn)程執(zhí)行前被子進(jìn)程調(diào)用,只用于Unix. close_fds: 如果設(shè)置為True, 則在子進(jìn)程被執(zhí)行前,除0,1和2之外的所有文件描述符都將被關(guān)閉,只用于Unix。 cwd: 當(dāng)它不為 None時,子程序在執(zhí)行前,它的當(dāng)前路徑會被替換成 cwd的值。 這個路徑并不會被添加到可執(zhí)行程序的搜索路徑,所以cwd不能是相對路徑。 env: 當(dāng)它不為 None時,它是新進(jìn)程的環(huán)境變量的映射。 可以用它來代替當(dāng)前進(jìn)程的環(huán)境。 universal_newlines: 為真時,文件對象 stdout和 stderr都被以文本文件的方式打開 示例代碼: 1. Popen對象創(chuàng)建后,主程序不會自動等待子進(jìn)程完成。 我們必須調(diào)用對象的wait()方法,父進(jìn)程才會等待 (也就是阻塞block): import subprocess child = subprocess.Popen(["ping","-c","5","www.google.com"]) print("parent process") 從運(yùn)行結(jié)果中看到,父進(jìn)程在開啟子進(jìn)程之后并沒有等待child的完成,而是直接運(yùn)行print。 2. 對比等待的情況: import subprocess child = subprocess.Popen(["ping","-c","5","www.google.com"]) child.wait() print("parent process") 此外,你還可以在父進(jìn)程中對子進(jìn)程進(jìn)行其它操作,比如我們上面例子中的child對象: child.poll() # 檢查子進(jìn)程狀態(tài) child.kill() # 終止子進(jìn)程 child.send_signal() # 向子進(jìn)程發(fā)送信號 child.terminate() # 終止子進(jìn)程 子進(jìn)程的PID存儲在child.pid 3. 可以在Popen()建立子進(jìn)程的時候改變標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤, 并可以利用subprocess.PIPE將多個子進(jìn)程的輸入和輸出連接在一起,構(gòu)成管道(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實際上為文本流提供一個緩存區(qū)。 child1的stdout將文本輸出到緩存區(qū),隨后child2的stdin從該P(yáng)IPE中將文本讀取走。 child2的輸出文本也被存放在PIPE中,直到communicate()方法從PIPE中讀取出PIPE中的文本。 要注意的是,communicate()是Popen對象的一個方法,該方法會阻塞父進(jìn)程,直到子進(jìn)程完成。 4. 還可以利用communicate()方法來使用PIPE給子進(jìn)程輸入: import subprocess child = subprocess.Popen(["cat"], stdin=subprocess.PIPE) child.communicate("vamei") 我們啟動子進(jìn)程之后,cat會等待輸入,直到我們用communicate()輸入"vamei"。 通過使用subprocess包,我們可以運(yùn)行外部程序。這極大的拓展了Python的功能。 如果你已經(jīng)了解了操作系統(tǒng)的某些應(yīng)用,你可以從Python中直接調(diào)用該應(yīng)用(而不是完全依賴Python), 并將應(yīng)用的結(jié)果輸出給Python,并讓Python繼續(xù)處理。 shell的功能(比如利用文本流連接各個應(yīng)用),就可以在Python中實現(xiàn)。 1.1.3.異常在開始執(zhí)行新程序之前,子進(jìn)程拋出的異常,會被重新拋出到父進(jìn)程。另外,異常對象會有一個額外的屬性,叫做 child_traceback, 它是一個字符串,包含從子程序的觀察點追蹤到的信息。 最常見的拋出的異常是 OSError, 當(dāng)它發(fā)生時,通常是我們執(zhí)行了一個不存在的文件。應(yīng)用程序應(yīng)當(dāng)要能處理這個異常。 如果使用無效的參數(shù)調(diào)用 Popen,會拋出 ValueError異常。 如果被調(diào)用進(jìn)程的返回碼不為零,則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. |
|
來自: 浸心閣 > 《subprocess》