标準類庫-并行執行之subprocess-子程序管理
by:授客QQ:1033553122
1.使用subprocess子產品
以下函數是調用子程序的推薦方法,所有使用場景它們都能處理。也可用Popen以滿足更進階的使用場景
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
運作args描述的指令,等待指令完成後傳回returncode屬性。
timeout參數會傳遞Popen.wait()。如果超過timeout,子程序将會被kill掉,并再次等待。子程序被終止後會抛出TimeoutExpired異常。
Eg:
>>>returncode = subprocess.call('exit 1', shell=True)
print(returncode)# 輸出1
>>> returncode = subprocess.call('exit 0', shell=True)
print(returncode)# 輸出0
注意:針對該函數,不要使用stdout=PIPE 或 stderr=PIPE。因為不是從目前程序中讀取管道(pipe),如果子程序沒有生成足夠的輸出來填充OS的管道緩沖區,可能會阻塞子程序。
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
運作攜帶參數的指令,等待指令完成。如果傳回代碼為0,則傳回,否則抛出 CalledProcessError。傳回代碼将被指派給CalledProcessError的returncode屬性
>>> subprocess.check_call(["ls", "-l"]) # run on linux only
>>> subprocess.check_call('exit 0', shell=True)
>>> subprocess.check_call('exit 1', shell=True)
Traceback (most recent call last):
……
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)
運作攜帶參數的指令,并傳回輸出
如果傳回代碼為b不為0,則抛出 CalledProcessError。傳回代碼将被指派給CalledProcessError的returncode屬性,且任意輸出将會被存放在output屬性。
>>> subprocess.check_output(['echo', 'hello world'], shell=True)
b'"hello world"\r\n'
>>> subprocess.check_output(['echo', 'hello world'], universal_newlines=True, shell=True)
'"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
>>>
預設的,該函數會傳回編碼的位元組。實際輸出的編碼可能依賴被調用的指令。 是以,對于輸出text的解碼經常需要在應用層處理。可通過設定universal_newlines 為True來覆寫編碼行為。
也可以通過使用stderr=subprocess.STDOUT在結果中捕獲标準錯誤。
>>> subprocess.check_output(
'dir non_existent_dir | exit 0',
stderr=subprocess.STDOUT,
universal_newlines=True,
shell=True)
'找不到檔案\n'
注意:針對該函數,不要使用stderr=PIPE。因為不是從目前程序中讀取管道(pipe),如果子程序沒有生成足夠的輸出來填充OS的管道緩沖區,可能會阻塞子程序。
subprocess.DEVNULL
可用于Popen函數stdin,stdout或者stderr參數的特定值,表示使用指定檔案os.devnull
subprocess.PIPE
可用于Popen函數stdin,stdout或者stderr參數的指特定值,表示必須打開一個指向标準流的管道。
subprocess.STDOUT
可用于Popen函數stdin,stdout或者stderr參數的指特定值,表示标準錯誤資訊必須一起寫入同樣的句柄,比如标準輸出。
exception subprocess.SubprocessError
來自該子產品的所有異常的父類。
exception subprocess.TimeoutExpired
SubprocessError的子類,當等待子程序timeout逾時抛出
cmd
用于衍生子程序的指令。
timeout
以秒wield機關的逾時時間。
output
如果異常由check_output抛出,則存放子程序的輸出。否則None
exception subprocess.CalledProcessError
SubprocessError的子類,當check_call() 或check_output()運作的程序退出時,傳回非0值時抛出。
returncode
子程序的退出狀态
2.頻繁使用的參數
以下是Popen,call,check_call,check_output等函數最常使用的參數:
args 所有調用的必填參數,參數值為字元串、序列。處于友善,通常更偏向于提供序列。如果傳遞的是單一字元串,shell參數值必須為True,如果不提供其它任何參數,傳遞單一字元串的情況下,該字元串必須是需要執行的程式名。
stdin, stdout,stderr分别指明了被執行程式的标準輸入,标準輸出和标準錯誤處理檔案句柄。可選值PIPE,DEVNULL,已存在檔案描述符(一個正整數),已存在檔案對象,None。PIPE表示應該建立通往子程序的管道。DEVNULL表示應該使用指定檔案os.devnull。預設參數None則表示無進行重定向,子程序檔案句柄從父程序繼承。此外,stderr還可以是STDOUT,表明子程序的錯誤資料應該被放進相同的檔案句柄stdout
如果universal_newlines為True,檔案對象stdin,stdout,stderr将按universal newlines(Unix 行結束符:'\n', Windows行結束符:'\r\n')模式,使用locale.getpreferredencoding(false)(函數會根據使用者偏好設定,傳回使用的文本資料的編碼)傳回的編碼,以檔案流的方式打開。
如果shell為True,指定指令将通過shell執行。出于安全考慮,如果指令字元串參數需要通過外部的輸入來構成的時候,強烈建議設定shell=False,不然容易造成shell注入之類的,如下
from subprocess import call
if __name__ == '__main__':
dirname = input('which dir would you like to cd in?\n')
call('cd ' + dirname, shell=True)
運作結果

3. 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, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=())
在一個新程序中執行子程序。類似在Unix上使用os.execvp(),Windows上使用CreateProcess()函數。
args 參數值為字元串、序列。預設的,如果args是個序列,程式會執行args中第一項。如果args是字元串則根據平台而異,如下所述。無特殊需求,建議傳遞序列。
注意:可用shlex.split()來判斷正确的args分割,特别是複雜的情況
>>> import shlex, subprocess
>>> command_line = 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'"]
>>> subprocess.Popen(args)
在Windows上,如果args為序列,那麼将會按照以下規則進行轉換為一個字元串,因為背景函數 CreateProcess() 的操作依賴字元串。
- 參數由空白字元(空格或tab)分隔。
- 通過雙引号标記的字元串被解釋為單個參數,不管字元串中是否包含空白字元。
- \"被當作是字面字元 ",即轉義字元
- 除非\後面跟了雙引号,如\",否則還是被解釋為字面字元 \
- \\被解釋為\,\\\"則被解釋為一個 \和一個 " 字元
shell參數用于指明是否使用shell作為執行程式。預設False。如果sell為True,則推薦傳遞字元串參數給args
Unix作業系統上,shell=True,shell預設為/bin/sh。如果args為字元串,則字元串指明了需要通過shell執行的指令。這意味着字元串必須具備準确的格式,正如在shell終端中輸入的一樣。如果args為序列,則第一項指定指令字元串,其它額外項則被當作額外參數,等同于說
Popen(['/bin/sh', '-c', args[0], args[1], ...])
Windows如果shell=True,COMSPCE環境變量指定了預設的shell。僅在command 指令需通過shell執行,比如dir,copy指令時,使用shell=True。不必要通過設定shell=True,來運作一批處理或者基于控制的可執行程式。
bufsize 當建立stdin/stdout/stderr管道檔案對象時,bufsize将作為io.open()函數的對應的參數: 0 - 意味着未緩沖 (means unbuffered (read and write are one system call and can return short))
1 - 意味着行緩沖(means line buffered)
任意正數 - 使用緩沖,緩沖大小和給定正數大緻相等。
任意負數 - 使用緩沖,緩沖大小等于系統自帶的o.DEFAULT_BUFFER_SIZE
Executable
executable參數指定了用于執行的替代程式。很少用到。
stdin, stdout 和stderr
分别指定被執行程式的标準輸入,标準輸出,标準錯誤檔案句柄。合法值為PIPE,DEVNULL,已存在檔案描述符(一個正整數),已存在檔案對象和None。 PIPE表示應該建立通往子程序的管道。DEVNULL表示應該使用指定檔案os.devnull。預設參數None則表示無進行重定向,子程序檔案句柄從父程序繼承。此外,stderr還可以是STDOUT,表明子程序的錯誤資料應該被放進相同的檔案句柄stdout
preexec_fn
如果preexec_fn 被設定為可調用對象,該對象将在子程序執行之前被執行(僅限Unix)。
close_fds
如果close_fds為True, 所有檔案描述符,0,1,2除外都在子程序執行前被關閉(僅限Unix)。 (Unix only). 預設值根據平台而異。Unix平台總是True。Windows平台,當stdin/stdout/stderr為None時,為True,否則為False。Windows平台,如果close_fds為True,那麼子程序不會繼承任何句柄。
universal_newlines
……
可配合with使用,退出時,先關閉标準檔案描述符,如下
import subprocess
if __name__ == '__main__':
with subprocess.Popen(['dir'], stdout=subprocess.PIPE, shell=True, universal_newlines = True) as proc:
print(proc.stdout.read())
輸出
更多參考官方文檔
4.Popen對象
Popen類執行個體有以下幾個方法
Popen.poll()
檢測子程序是否中斷,設定并傳回returncode
Popen.wait(timeout=None)
等待子程序終止,設定并傳回returncode。如果程序在timeout(機關 秒)之後依然沒終止,則抛出TimeoutExpired 異常,可以捕獲該異常并再次嘗試等待。
警告
當使用stdout=PIPE and/or stderr=PIPE時,如果子程序生成足夠的輸出到管道,這會阻止作業系統管道緩沖區接收更多資料,進而造成死鎖。為了避免該事件,使用communicate()
Popen.communicate(input=None, timeout=None)
和process互動:發送資料到stdin,從stdout,stderr讀取資料,直到檔案結束符。等待子程序終止。
input:可選參數,參數值為發送給子程序的資料,如果不需要發送資料,則為None。如果universal_newlines為False,則input資料類型必須為位元組,否則可為字元串。
函數傳回一個元組(stdoutdata, stderrdata)
注意,如果想發送資料到程序管道,必須在建立Popen對象時使用stdin=PIPE,類似的如果想從結果元組中擷取非None值資料,建立Popen對象時需要提供stdout=PIPE and/or stderr=PIPE參數。
如果程序在timeout(機關 秒)之後依然沒終止,則抛出TimeoutExpired 異常,(Python3.3.2中發絲。捕獲該異常并重試comunicate,不會丢失任何輸出。
如果超過timeout,子程序不會被kill掉,是以為了完成互動,恰當的清理友好執行的程式,應該kill子程序。
import subprocess
if __name__ == '__main__':
with subprocess.Popen(['dir'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines = True) as proc:
try:
outs, errs = proc.communicate(timeout=15) #逾時時間為15秒
print(outs, errs)
except subprocess.TimeoutExpired:
proc.kill()
outs, errs = proc.communicate()
print(outs, errs)
注意:讀取的資料緩存在記憶體,是以如果資料太大或者無限制,不要使用該函數。
Popen.send_signal(signal)
發送signal給子程序
Popen.terminate()
停止子程序。
Popen.kill()
Kill子程序。 Posix作業系統:函數會發送SIGKILL給子程序。Windows,kill()為terminate()别名。
以下為屬性:
注意:使用communicate() 而非.stdin.write, .stdout.read 或者.stderr.read以避免死鎖。
Popen.stdin
如果stdin參數為PIPE,該屬性為給子程序提供輸入的檔案對象, 否則為None.
Popen.stdout
如果stdin參數為PIPE,該屬性為給子程序提供正确輸出的檔案對象,否則為None.
Popen.stderr
如果stdin參數為PIPE,該屬性為給子程序提供錯誤輸出的檔案對象,否則為None.
Popen.pid
子程序的ID。
注意:如果設定了shell=True,則該屬性值為衍生的shell程序的id
Popen.returncode
子程序傳回代碼,如果值為None表明程序還沒終止。負值-N表示子程序通過signal N終止的(僅限Unix)
import sys
import subprocess
def run_command():
cmd = [sys.executable, 'py1.py']
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines = True, stderr=subprocess.STDOUT)
outs, errs = proc.communicate()
# print(proc.communicate()) #報錯,因為檔案已經關閉
print(outs)
if __name__ == '__main__':
run_command()
運作結果:
注:py1.py和study.py在同一個目錄下,内容如下
#!/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'laiyu'
print('output from py1.py')
5.替換老函數
可用call、Popen替換一些老函數如os.system(),os.spawn家族系列,shell管道等,具體參考官方文檔
作者:授客
QQ:1033553122
全國軟體測試QQ交流群:7156436
Git位址:https://gitee.com/ishouke
友情提示:限于時間倉促,文中可能存在錯誤,歡迎指正、評論!
作者五行缺錢,如果覺得文章對您有幫助,請掃描下邊的二維碼打賞作者,金額随意,您的支援将是我繼續創作的源動力,打賞後如有任何疑問,請聯系我!!!
微信打賞
支付寶打賞 全國軟體測試交流QQ群