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