subprocess 是 Python 中執行作業系統級別的命令的模組,所謂系級級別的命令就是如ls /etc/user ifconfig 等和作業系統有關的命令。
subprocess 建立子程序來執行相關命令,並連線它們的輸入、輸出和錯誤管道,獲取它們的返回狀態。
Subprocess模組開發之前,標準庫已有大量用於執行系統級別命令的的方法,如os.system、os.spawn等。但是略顯混亂使開發者難以抉擇,因此subprocess的目的是打造一個統一模組來替換之前執行系統界別命令的方法。
所以 推薦使用subprocess替代了一些老的方法,比如:os.system、os.spawn*等。
Subprocess 模組推薦使用run方法替換低版本方法,如果想要更加精細的控制可以使用Popen方法。所以本教學中重點介紹run和Popen方法。
函數簽名
subprocess.run(
args,
*,
stdin=None,
input=None,
stdout=None,
stderr=None,
capture_output=False,
shell=False,
cwd=None,
timeout=None,
check=False,
encoding=None,
errors=None,
text=None,
env=None,
universal_newlines=None,
**other_popen_kwargs
)
簡單使用
執行簡單shell命令
預設情況下,子程序會繼承父程序的設定,會將輸出顯示在終端上
import subprocess
res = subprocess.run("ls -al /home/ljk/Videos", shell=True)
>>>
(ymir) ➜ subprocess_demo python3 subprocess_demo.py
總用量 96
drwxr-xr-x 3 ljk ljk 4096 4月 11 11:04 .
drwxr-x--- 62 ljk ljk 4096 7月 6 13:40 ..
-rw-r--r-- 1 ljk ljk 84176 4月 11 11:04 346e30f4-9119-11eb-bb4a-4a238cf0c417.mp4
lrwxrwxrwx 1 ljk ljk 36 10月 21 2022 dde-introduction.mp4 -> /usr/share/dde-introduction/demo.mp4
drwxr-xr-x 2 ljk ljk 4096 4月 11 18:26 'Screen Recordings'
如果命令沒有輸出則不會列印輸出資訊
獲取狀態碼
returncode 是subprocess的返回碼
import subprocess
res = subprocess.run("ls -al /home/ljk/Videos", shell=True)
print("returncode:", res.returncode)
>>>
總用量 96
drwxr-xr-x 3 ljk ljk 4096 7月 6 13:41 .
drwxr-x--- 62 ljk ljk 4096 7月 6 13:47 ..
-rw-r--r-- 1 ljk ljk 84176 4月 11 11:04 346e30f4-9119-11eb-bb4a-4a238cf0c417.mp4
-rw-r--r-- 1 ljk ljk 0 7月 6 13:41 a.txt
lrwxrwxrwx 1 ljk ljk 36 10月 21 2022 dde-introduction.mp4 -> /usr/share/dde-introduction/demo.mp4
drwxr-xr-x 2 ljk ljk 4096 4月 11 18:26 'Screen Recordings'
0
args:要執行的命令,可以是字串形式或由命令及其引陣列成的列表。例如,['ls', '-l'] 或 'ls -l'。
input:允許將位元組或字串傳遞給子程序的標準輸入(stdin)。
stdin:子程序的標準輸入。預設為None,可以是以下三個引數:
stdout: 同 stdin
stderr: 同 stdin
capture_output :這個引數控制是否捕獲外部命令的標準輸出(stdout)和標準錯誤(stderr)。如果將其設定為True,run()函數將返回一個CompletedProcess物件,該物件具有stdout和stderr屬性,分別儲存了命令的標準輸出和標準錯誤輸出。如果設定為False,標準輸出和標準錯誤將被傳送到控制檯。預設為False。
shell:指定是否通過shell來執行命令。如果為True,命令將在shell中執行;如果為False,則直接呼叫可執行檔案。預設為False。
cwd:設定子程序的工作目錄。預設為None,表示使用當前工作目錄。
timeout:設定子程序的超時時間(秒)。如果子程序在指定的時間內沒有執行完成,則會引發TimeoutExpired異常。
check:設定是否檢查子程序的返回碼。如果為True,並且子程序的返回碼不為零,則會引發CalledProcessError異常。
encoding:該引數指定輸出結果的字元編碼。預設情況下,它是None,表示使用原始的位元組資料。如果提供了有效的編碼名稱(如"utf-8"、"gbk"等),run()函數將自動將輸出解碼為字串。
errors:該引數定義在解碼輸出時如何處理編碼錯誤。它與Python的str.decode()函數的相同引數含義相匹配。常用的值包括"strict" (預設值,丟擲異常)、"ignore" (忽略錯誤字元) 和 "replace" (用替代字元代替錯誤字元)。
text:指定是否將輸出結果以文字形式返回。如果為True,則結果以字串形式返回,同時input或者stdin引數也需要輸入String;如果為False,則返回位元組流。預設為False。
env:該引數允許您為子程序指定環境變數。它可以接受一個字典型別的物件,其中鍵是環境變數的名稱,值是環境變數的值。通過設定env引數,您可以在子程序中使用特定的環境變數。
universal_newlines: 該引數影響的是輸入與輸出的資料格式,比如它的值預設為False,此時stdout和stderr的輸出是位元組序列;當該引數的值設定為True時,stdout和stderr的輸出是字串。
args傳入的是要執行的系統命令,可以接收兩種方法:字串或列表。
import subprocess
subprocess.run(["ls", "-al", "/Users/ljk/Documents/code/daily_dev"])
subprocess.run("ls -al /Users/ljk/Documents/code/daily_dev", shell=True)
>>>
➜ subprocess_demo python3 subprocess_demo.py
total 0
drwxr-xr-x 5 ljk staff 160 7 11 22:27 .
drwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..
drwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev
drwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo
drwxr-xr-x 4 ljk staff 128 7 11 22:30 subprocess_demo
total 0
drwxr-xr-x 5 ljk staff 160 7 11 22:27 .
drwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..
drwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev
drwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo
drwxr-xr-x 4 ljk staff 128 7 11 22:30 subprocess_demo
預設情況下,命令的輸出是直接列印到控制檯上的。
這三個值是用來設定標準輸入,標準輸出,標準錯誤的。預設情況下,子程序會繼承父程序的設定,會將輸出顯示在控制檯上,除此之外也可以設定成如下三個值:
將命令輸出儲存到管道
import subprocess
res = subprocess.run(["ls", "-al", "/Users/ljk/Documents/code/daily_dev"], stdout=subprocess.PIPE)
print(res.returncode)
print(res.stdout)
>>>
0
b'total 0\ndrwxr-xr-x 5 ljk staff 160 7 11 22:27 .\ndrwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..\ndrwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev\ndrwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo\ndrwxr-xr-x 4 ljk staff 128 7 11 22:44 subprocess_demo\n'
命令輸出不再列印到控制檯上,而是儲存到物件裡,通過物件的stdout獲取到。此時命令輸出結果是位元組串格式的。可以通過設定text=True,將命令輸出以文字形式儲存。
import subprocess
res = subprocess.run(["ls", "-al", "/Users/ljk/Documents/code/daily_dev"], stdout=subprocess.PIPE, text=True)
print(res.returncode)
print(res.stdout)
>>>
0
total 0
drwxr-xr-x 5 ljk staff 160 7 11 22:27 .
drwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..
drwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev
drwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo
drwxr-xr-x 4 ljk staff 128 7 11 22:48 subprocess_demo
命令輸出儲存到檔案中
可以將命令的輸出儲存到一個檔案中,stdout傳入一個開啟的檔案物件即可。
import subprocess
with open("a.txt", "a+") as f:
res = subprocess.run(["ls", "-al", "/Users/ljk/Documents/code/daily_dev"], stdout=f, text=True)
print(res.returncode)
print(res.stdout)
>>>
0
None
此時在目錄下生成了a.txt檔案,裡面儲存的是命令的輸出結果
捕獲命令輸出。預設為false,所有的命令輸出都列印到控制檯。設定為true,所有命令的輸出都被捕獲儲存到返回物件中。
import subprocess
res = subprocess.run("ls -al /Users/ljk/Documents/code/daily_dev", shell=True, capture_output=True)
print(res.returncode)
print(res.stdout)
>>>
0
b'total 0\ndrwxr-xr-x 5 ljk staff 160 7 11 22:27 .\ndrwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..\ndrwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev\ndrwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo\ndrwxr-xr-x 5 ljk staff 160 7 13 20:38 subprocess_demo\n'
設定子程序的工作目錄。預設為None,表示使用當前工作目錄
import subprocess
res = subprocess.run("pwd", shell=True, cwd="/Users/ljk/Documents/code/")
print(res.returncode)
/Users/ljk/Documents/code
0
如果指令碼需要在特定的目錄中執行,可以設定該引數
當一些命令有時間上的要求,可以設定命令執行的超時時間。如果命令在指定的時間內沒有執行完成,則會引發TimeoutExpired異常。
import subprocess
res = subprocess.run("sleep 10 && ls", shell=True, timeout=5)
print(res.returncode)
>>>
Traceback (most recent call last):
File "/Users/ljk/Documents/code/daily_dev/subprocess_demo/subprocess_demo.py", line 22, in <module>
res = subprocess.run("sleep 10 && ls", shell=True, timeout=5)
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 507, in run
stdout, stderr = process.communicate(input, timeout=timeout)
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 1134, in communicate
stdout, stderr = self._communicate(input, endtime, timeout)
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 2005, in _communicate
self.wait(timeout=self._remaining_time(endtime))
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 1189, in wait
return self._wait(timeout=timeout)
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 1909, in _wait
raise TimeoutExpired(self.args, timeout)
subprocess.TimeoutExpired: Command 'sleep 10 && ls' timed out after 4.999915208999999 seconds
執行的命令是先睡眠10s,然後執行ls。設定的超時時間是5秒,所以執行的第5s就丟擲timeout錯誤。
檢查子程序的返回碼。如果為True,並且子程序的返回碼不為零,則會引發CalledProcessError異常。以下程式碼做了一組對比,ls一個不存在目錄,設定check=True的會丟擲異常。
res = subprocess.run("ls no_exsit.txt", shell=True)
print(res.returncode)
>>>
ls: no_exsit.txt: No such file or directory
1
res = subprocess.run("ls no_exsit.txt", shell=True, check=True)
print(res.returncode)
>>>
ls: no_exsit.txt: No such file or directory
Traceback (most recent call last):
File "/Users/ljk/Documents/code/daily_dev/subprocess_demo/subprocess_demo.py", line 25, in <module>
res = subprocess.run("ls no_exsit.txt", shell=True, check=True)
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 528, in run
raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command 'ls no_exsit.txt' returned non-zero exit status 1.
encoding 用於設定命令輸出的編碼格式。 預設情況下,它是None,表示使用原始的位元組資料。如果提供了有效的編碼名稱,如"utf-8"、"gbk",將自動將輸出解碼為字串。範例演示encoding=True
# 原始輸出
res = subprocess.run("ls -al /Users/ljk/Documents/code/daily_dev", shell=True, capture_output=True)
print(res.returncode)
print(res.stdout)
>>>
0
b'total 0\ndrwxr-xr-x 5 ljk staff 160 7 11 22:27 .\ndrwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..\ndrwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev\ndrwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo\ndrwxr-xr-x 5 ljk staff 160 7 13 21:07 subprocess_demo\n'
# 設定encoding="utf-8"
res = subprocess.run("ls -al /Users/ljk/Documents/code/daily_dev", shell=True, capture_output=True, encoding="utf-8")
print(res.returncode)
print(res.stdout)
>>>
0
total 0
drwxr-xr-x 5 ljk staff 160 7 11 22:27 .
drwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..
drwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev
drwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo
drwxr-xr-x 5 ljk staff 160 7 13 21:07 subprocess_demo
text 引數是用於設定命令輸出的格式。命令輸出預設是位元組串,text=True表示輸出格式為字串。和encoding=True 基本等價。
# 原始輸出
res = subprocess.run("ls -al /Users/ljk/Documents/code/daily_dev", shell=True, capture_output=True)
print(res.returncode)
print(res.stdout)
>>>
0
b'total 0\ndrwxr-xr-x 5 ljk staff 160 7 11 22:27 .\ndrwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..\ndrwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev\ndrwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo\ndrwxr-xr-x 5 ljk staff 160 7 13 21:10 subprocess_demo\n'
# 設定text=True
res = subprocess.run("ls -al /Users/ljk/Documents/code/daily_dev", shell=True, capture_output=True, text=True)
print(res.returncode)
print(res.stdout)
>>>
0
total 0
drwxr-xr-x 5 ljk staff 160 7 11 22:27 .
drwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..
drwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev
drwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo
drwxr-xr-x 5 ljk staff 160 7 13 21:10 subprocess_demo
subprocess.run()函數返回值是一個CompletedProcess類的範例,subprocess.completedPorcess類是Python 3.5以上才存在的。它表示的是一個已結束程序的狀態資訊,它所包含的屬性和方法如下:
args: 用於載入該程序的引數,這可能是一個列表或一個字串。
returncode: 子程序的退出狀態碼。通常情況下,退出狀態碼為0則表示程序成功執行了;一個負值-N表示這個子程序被訊號N終止了。
stdout: 從子程序捕獲的stdout。這通常是一個位元組串。如果設定了encoding或text引數,返回就是字串。
stderr: 從子程序捕獲的stderr。它的值與stdout一樣,是一個位元組序列或一個字串。如果stderr沒有被捕獲的話,它的值就為None。
check_returncode(): 如果returncode是一個非0值,則該方法會丟擲一個CalledProcessError異常。
範例:
import subprocess
res = subprocess.run("ls -al /home/ljk/Videos", shell=True)
print("args:", res.args)
print("returncode:", res.returncode)
print("stdout:", res.stdout)
print("stderr:", res.stderr)
print("returncode():", res.check_returncode())
>>>
➜ subprocess_demo python3 subprocess_demo.py
總用量 96
drwxr-xr-x 3 ljk ljk 4096 7月 6 13:41 .
drwxr-x--- 62 ljk ljk 4096 7月 6 13:47 ..
-rw-r--r-- 1 ljk ljk 84176 4月 11 11:04 346e30f4-9119-11eb-bb4a-4a238cf0c417.mp4
-rw-r--r-- 1 ljk ljk 0 7月 6 13:41 a.txt
lrwxrwxrwx 1 ljk ljk 36 10月 21 2022 dde-introduction.mp4 -> /usr/share/dde-introduction/demo.mp4
drwxr-xr-x 2 ljk ljk 4096 4月 11 18:26 'Screen Recordings'
args: ls -al /home/ljk/Videos
returncode: 0
stdout: None
stderr: None
returncode(): None
popen是一個功能更強大的方法,而run是它的一個簡化版。如果run函數不能滿足功能的要求,可以嘗試功能更多的popen方法。
除了方法的多少之外,run和popen最大的區別在於:run方法是阻塞呼叫,會一直等待命令執行完成或失敗;popen是非阻塞呼叫,執行之後立刻返回,結果通過返回物件獲取。
popen函數簽名:
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=None,
startupinfo=None,
creationflags=0,
restore_signals=True,
start_new_session=False,
pass_fds=(),
*,
group=None,
extra_groups=None,
user=None,
umask=- 1,
encoding=None,
errors=None,
text=None,
pipesize=- 1,
process_group=None
)
和run一樣執行命令
import subprocess
subprocess.Popen("ls -al /Users/ljk/Documents/code/daily_dev", shell=True)
>>>>
None
total 0
drwxr-xr-x 5 ljk staff 160 7 11 22:27 .
drwxr-xr-x@ 18 ljk staff 576 7 3 22:11 ..
drwxr-xr-x 3 ljk staff 96 6 24 18:28 docker_dev
drwxr-xr-x 3 ljk staff 96 6 17 22:08 requests_demo
drwxr-xr-x 5 ljk staff 160 7 13 21:32 subprocess_demo
執行阻塞命令
import subprocess
res = subprocess.Popen("sleep 10 && ls -al", shell=True)
print(res)
>>>
<Popen: returncode: None args: 'sleep 10 && ls -al'>
遇到阻塞命令也會直接返回,返回是一個物件。可以通過物件獲取命令執行的結果。
注意:因為run是popen的一個簡化版本,所以run擁有的函數popen也擁有。這裡就不再重複說明了。
bufsize:定義了子程序的緩衝大小。可選引數,預設為-1,表示使用系統預設的緩衝大小。
executable:指定要執行的程式路徑。如果未提供該值,則通過PATH環境變數來確定可執行檔案的位置。
preexec_fn:指定在子程序啟動之前將要執行的函數。該函數將在fork()呼叫成功,但exec()呼叫之前被呼叫。
close_fds:指定是否關閉所有檔案描述符。預設為False。
start_new_session(僅 POSIX):如果該引數設定為True,則在啟動子程序時建立一個新的程序對談。預設為False。
pass_fds(僅 POSIX):通過這個引數傳遞一個檔案描述符集合,這些檔案描述符將保持開啟狀態並傳遞給子程序。預設為None。
startupinfo:一個可選的subprocess.STARTUPINFO物件,用於指定子程序的啟動資訊,如視窗大小、視窗標題等。預設為None。
creationflags:用於指定子程序的建立標誌,控制子程序的各種行為。可以使用subprocess.CREATE_NEW_CONSOLE、subprocess.CREATE_NEW_PROCESS_GROUP等常數進行設定。預設為0。
restore_signals(僅 POSIX):用於確定是否在子程序中恢復訊號處理程式的預設行為。預設為True。
group(僅 POSIX): 如果 group 不為 None,則 setregid() 系統呼叫將於子程序執行之前在下級程序中進行。 如果所提供的值為一個字串,將通過 grp.getgrnam() 來查詢它,並將使用 gr_gid 中的值。 如果該值為一個整數,它將被原樣傳遞。 (POSIX 專屬)
extra_groups(僅 POSIX): 如果 extra_groups 不為 None,則 setgroups() 系統呼叫將於子程序之前在下級程序中進行。 在 extra_groups 中提供的字串將通過 grp.getgrnam() 來查詢,並將使用 gr_gid 中的值。 整數值將被原樣傳遞。
user(僅 POSIX): 如果 user 不為 None,則 setreuid() 系統呼叫將於子程序執行之前在下級程序中進行。 如果所提供的值為一個字串,將通過 pwd.getpwnam() 來查詢它,並將使用 pw_uid 中的值。 如果該值為一個整數,它將被原樣傳遞。 (POSIX 專屬)
communicate(input=None, timeout=None): 與子程序進行互動,傳送輸入並獲取輸出結果。可以在引數input中指定要傳送給子程序的輸入內容。該方法會阻塞當前程序,直到子程序完成並返回輸出結果。可選的timeout引數用於設定超時時間。
poll(): 檢查子程序是否已經退出,如果已退出則返回退出狀態碼,否則返回None。
wait(timeout=None): 等待子程序完成並返回退出狀態碼。可選的timeout引數用於設定超時時間。
terminate(): 向子程序傳送終止訊號。這通常是優雅地終止子程序。
kill(): 強制終止子程序。
send_signal(signal): 向子程序傳送訊號,其中signal參數列示要傳送的訊號型別,如SIGINT、SIGTERM等。
傳送輸入並獲取輸出結果。可以在引數input中指定要傳送給子程序的輸入內容。該方法會阻塞當前程序,直到子程序完成並返回輸出結果。函數返回一個元組: (stdoutdata , stderrdata )
import subprocess
res = subprocess.Popen("sleep 3 && ls -al", shell=True)
print(res.communicate())
>>>
total 24
drwxr-xr-x 5 ljk staff 160 7 13 21:40 .
drwxr-xr-x 5 ljk staff 160 7 11 22:27 ..
-rw-r--r-- 1 ljk staff 269 7 11 22:50 a.txt
-rwxrwxrwx 1 ljk staff 42 7 11 22:29 ls_demo.sh
-rw-r--r-- 1 ljk staff 1069 7 13 21:40 subprocess_demo.py
(None, None)
該方法和run函數行為一致,將非阻塞呼叫變成阻塞呼叫。
subprocess.Popen().communicate() 等價於 subprocess.run()
Poll 檢查子程序是否已經退出,如果已退出則返回退出狀態碼,否則返回None。
import time
res = subprocess.Popen("sleep 3 && ls -al", shell=True)
print(res.poll())
time.sleep(4)
print(res.poll())
>>>>
None
total 24
drwxr-xr-x 5 ljk staff 160 7 13 21:39 .
drwxr-xr-x 5 ljk staff 160 7 11 22:27 ..
-rw-r--r-- 1 ljk staff 269 7 11 22:50 a.txt
-rwxrwxrwx 1 ljk staff 42 7 11 22:29 ls_demo.sh
-rw-r--r-- 1 ljk staff 1097 7 13 21:39 subprocess_demo.py
0
在執行命令之後立刻用poll檢查發現返回None,此時因為子程序還沒有退出。程式睡眠4s之後命令已經退出,再次執行poll返回了狀態碼。在兩次列印中間是命令的標準輸出。
res = subprocess.Popen("sleep 3 && ls -al", shell=True, stdout=subprocess.PIPE, encoding="utf-8")
while res.poll() is None:
time.sleep(0.5)
print("命令還在執行中...")
print("命令執行完成,獲取結果:")
print(res.communicate())
等待子程序完成並返回退出狀態碼。可選的timeout引數用於設定超時時間。
# 等待,不設定超時
import subprocess
res = subprocess.Popen("sleep 3 && ls -al", shell=True)
print(res.wait())
>>>
total 24
drwxr-xr-x 5 ljk staff 160 7 13 22:04 .
drwxr-xr-x 5 ljk staff 160 7 11 22:27 ..
-rw-r--r-- 1 ljk staff 269 7 11 22:50 a.txt
-rwxrwxrwx 1 ljk staff 42 7 11 22:29 ls_demo.sh
-rw-r--r-- 1 ljk staff 1071 7 13 22:04 subprocess_demo.py
0
# 等待,設定超時,報錯
res = subprocess.Popen("sleep 6 && ls -al", shell=True)
print(res.wait(timeout=5))
>>>
Traceback (most recent call last):
File "/Users/ljk/Documents/code/daily_dev/subprocess_demo/subprocess_demo.py", line 41, in <module>
print(res.wait(timeout=5))
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 1189, in wait
return self._wait(timeout=timeout)
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 1909, in _wait
raise TimeoutExpired(self.args, timeout)
subprocess.TimeoutExpired: Command 'sleep 6 && ls -al' timed out after 5 seconds
drwxr-xr-x 5 ljk staff 160 7 13 22:04 .
drwxr-xr-x 5 ljk staff 160 7 11 22:27 ..
-rw-r--r-- 1 ljk staff 269 7 11 22:50 a.txt
-rwxrwxrwx 1 ljk staff 42 7 11 22:29 ls_demo.sh
-rw-r--r-- 1 ljk staff 1071 7 13 22:04 subprocess_demo.py
wait 設定了超時,在指定時間之內沒有執行完成會丟擲異常,但是命令還是會在後端繼續執行,沒有停止
Terminate 可以終止一個還沒有執行完成的命令。wait設定超時之後雖然會丟擲異常,但是並不會終止命令。而terminate就可以優雅的終止命令。
import subprocess
res = subprocess.Popen("sleep 3 && ls -al", shell=True)
print(res.poll())
res.terminate()
>>>
None
如果沒有終止會列印輸出資訊,而終止之後就不會再列印出來了。所謂優雅可能是停止命令之前會關閉開啟的檔案,管道,通訊端等。
使用kill可以強制將執行的命令殺死,類似於linux系統中的kill命令。kill不會關閉已經開啟的檔案控制程式碼等。
import subprocess
res = subprocess.Popen("sleep 3 && ls -al", shell=True)
print(res.poll())
res.kill()
subprocess 會丟擲一些異常,自帶的異常捕獲模組可以完成相關異常的捕獲
異常型別:class subprocess.TimeoutExpired(cmd, timeout, output=None)
當子程序執行時間超過指定的超時時間時引發。
屬性:
異常型別:class subprocess.CalledProcessError(returncode, cmd, output=None, stderr=None)
在使用 check_output() 或 check_call() 函數執行外部命令並返回非零退出碼時引發。
屬性:
使用範例:
import subprocess
try:
res = subprocess.run("ls no_exsit.txt", shell=True, check=True)
except subprocess.CalledProcessError as e:
print("returncode:", e.returncode)
print("cmd:", e.cmd)
print("output:", e.output)
print("stderr:", e.stderr
>>>
ls: 無法存取'no_exsit.txt': 沒有那個檔案或目錄
returncode: 2
cmd: ls no_exsit.txt
output: None
stderr: None
這是其他subprocess常類的基礎類別,可以用於捕獲所有與子程序相關的異常。
import subprocess
return_code = subprocess.call(["ls", "-l"])
print(f"Command returned with exit code: {return_code}")
範例程式碼:
import subprocess
subprocess.check_call(["ls", "-l"])
print("Command executed successfully")
import subprocess
output = subprocess.getoutput("echo Hello, subprocess!")
print(output)
import subprocess
status, output = subprocess.getstatusoutput("ls -l")
print(f"Exit status: {status}")
print(f"Output: {output}")
import subprocess
output = subprocess.check_output(["ls", "-l"])
print(output.decode("utf-8"))
與os模組對比而言,subprocess模組具有以下這些優勢:
因此,當執行復雜的子程序操作、需要更多控制權和靈活性、以及考慮到安全性時,優先選擇subprocess模組是更好的選擇。而對於簡單的命令執行需求或與作業系統相關的功能,os模組可能更加適合。