簡介
subprocess 是 Python 中執行操作系統級別的命令的模塊,所謂系級級別的命令就是如ls /etc/user ifconfig 等和操作系統有關的命令。
subprocess 創建子進程來執行相關命令,並連接它們的輸入、輸出和錯誤管道,獲取它們的返回狀態。
subprocess 來源
Subprocess模塊開發之前,標準庫已有大量用於執行系統級別命令的的方法,如os.system、os.spawn等。但是略顯混亂使開發者難以抉擇,因此subprocess的目的是打造一個統一模塊來替換之前執行系統界別命令的方法。
所以 推薦使用subprocess替代了一些老的方法,比如:os.system、os.spawn*等。
模塊常用函數
函數清單總覽
Subprocess 模塊推薦使用run方法替換低版本方法,如果想要更加精細的控制可以使用Popen方法。所以本教程中重點介紹run和Popen方法。
subprocess.run()
函數簽名
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,可以是以下三個參數:
- subprocess.PIPE 創建一個管道,允許與子進程進行通信
- subprocess.DEVNULL 特殊的文件對象,可以將其用於丟棄子進程的輸出
- 一個打開的文件對象,將內容寫入文件
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 執行命令
args傳入的是要執行的系統命令,可以接收兩種方法:字符串或列表。
- 使用列表形式subprocess.run(["ls", "-al"])
- 使用字符串形式 subprocess.run("ls -al", shell=True)。使用字符串形式必須設置參數shell=True
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
默認情況下,命令的輸出是直接打印到控制檯上的。
stdin、stdout、sterr 設置命令輸出輸入的對象
這三個值是用來設置標準輸入,標準輸出,標準錯誤的。默認情況下,子進程會繼承父進程的設置,會將輸出顯示在控制檯上,除此之外也可以設置成如下三個值:
- subprocess.PIPE 創建一個管道,允許與子進程進行通信
- subprocess.DEVNULL 特殊的文件對象,可以將其用於丟棄子進程的輸出
- 一個打開的文件對象,將內容寫入文件
以studout爲例子,驗證這三個輸出選項。
將命令輸出保存到管道
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文件,裏面保存的是命令的輸出結果
capture_output 捕獲控制檯輸出
捕獲命令輸出。默認爲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'
cwd 設置命令執行的目錄
設置子進程的工作目錄。默認爲None,表示使用當前工作目錄
import subprocess
res = subprocess.run("pwd", shell=True, cwd="/Users/ljk/Documents/code/")
print(res.returncode)
/Users/ljk/Documents/code
0
如果腳本需要在特定的目錄中執行,可以設置該參數
timeout 設置命令超執行時時間
當一些命令有時間上的要求,可以設置命令執行的超時時間。如果命令在指定的時間內沒有運行完成,則會引發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錯誤。
check 返回碼非1拋出錯誤
檢查子進程的返回碼。如果爲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、text 設置輸出結果的格式
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
subprocess.Popen()
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 專屬)
Popen類的方法與參數介紹
communicate(input=None, timeout=None): 與子進程進行交互,發送輸入並獲取輸出結果。可以在參數input中指定要發送給子進程的輸入內容。該方法會阻塞當前進程,直到子進程完成並返回輸出結果。可選的timeout參數用於設置超時時間。
poll(): 檢查子進程是否已經退出,如果已退出則返回退出狀態碼,否則返回None。
wait(timeout=None): 等待子進程完成並返回退出狀態碼。可選的timeout參數用於設置超時時間。
terminate(): 向子進程發送終止信號。這通常是優雅地終止子進程。
kill(): 強制終止子進程。
send_signal(signal): 向子進程發送信號,其中signal參數表示要發送的信號類型,如SIGINT、SIGTERM等。
communicate 獲取命令輸出
發送輸入並獲取輸出結果。可以在參數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 檢查子進程
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())
wait 用於等待命令執行完成
等待子進程完成並返回退出狀態碼。可選的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 優雅的終止執行的命令
Terminate 可以終止一個還沒有執行完成的命令。wait設置超時之後雖然會拋出異常,但是並不會終止命令。而terminate就可以優雅的終止命令。
import subprocess
res = subprocess.Popen("sleep 3 && ls -al", shell=True)
print(res.poll())
res.terminate()
>>>
None
如果沒有終止會打印輸出信息,而終止之後就不會再打印出來了。所謂優雅可能是停止命令之前會關閉打開的文件,管道,套接字等。
kill 強制終止執行的命令
使用kill可以強制將執行的命令殺死,類似於linux系統中的kill命令。kill不會關閉已經打開的文件句柄等。
import subprocess
res = subprocess.Popen("sleep 3 && ls -al", shell=True)
print(res.poll())
res.kill()
異常捕獲
subprocess 會拋出一些異常,自帶的異常捕獲模塊可以完成相關異常的捕獲
TimeoutExpired
異常類型:class subprocess.TimeoutExpired(cmd, timeout, output=None)
當子進程執行時間超過指定的超時時間時引發。
屬性:
- cmd:執行的命令。
- timeout:設定的超時時間。
- output:子進程輸出的內容。
CalledProcessError
異常類型:class subprocess.CalledProcessError(returncode, cmd, output=None, stderr=None)
在使用 check_output() 或 check_call() 函數執行外部命令並返回非零退出碼時引發。
屬性:
- returncode:子進程的返回碼。
- cmd:已執行的命令。
- output:標準輸出的內容(如果沒有重定向則爲 None)
- stderr:標準錯誤的內容(如果沒有重定向則爲 None)
使用示例:
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.SubprocessError
這是其他subprocess常類的基類,可以用於捕獲所有與子進程相關的異常。
舊函數簡介
- subprocess.call()
函數執行給定的命令,並等待其完成。它返回命令的退出碼。
示例代碼:
import subprocess
return_code = subprocess.call(["ls", "-l"])
print(f"Command returned with exit code: {return_code}")
- subprocess.check_call()
check_call() 函數也執行給定的命令,但與 call() 不同的是,如果命令返回非零的退出碼,則會引發 CalledProcessError 異常。
示例代碼:
import subprocess
subprocess.check_call(["ls", "-l"])
print("Command executed successfully")
- subprocess.getoutput()
getoutput() 函數執行給定的命令,並返回其輸出作爲字符串。
示例代碼:
import subprocess
output = subprocess.getoutput("echo Hello, subprocess!")
print(output)
- subprocess.getstatusoutput()
getstatusoutput() 函數執行給定的命令,並返回一個元組,包含命令的退出狀態碼和輸出結果的字符串。
示例代碼:
import subprocess
status, output = subprocess.getstatusoutput("ls -l")
print(f"Exit status: {status}")
print(f"Output: {output}")
- subprocess.check_output()
check_output() 函數執行給定的命令,並返回其輸出結果作爲字節字符串。
示例代碼:
import subprocess
output = subprocess.check_output(["ls", "-l"])
print(output.decode("utf-8"))
subprocess 和 os 模塊比較
與os模塊對比而言,subprocess模塊具有以下這些優勢:
- 更豐富的功能
subprocess模塊提供了更多的方法和選項來執行子進程,並與其進行交互。例如,可以捕獲子進程的輸出、發送輸入數據、設置超時時間等。 - 更強的靈活性
subprocess模塊允許您以多種不同的方式執行子進程,包括使用管道、重定向輸入輸出、執行shell命令等。這使得您能夠更靈活地控制和處理子進程的輸入輸出。 - 更好的安全性
subprocess模塊提供了更嚴格的參數處理機制,可以幫助避免常見的安全問題,如命令注入攻擊。它支持傳遞參數列表而不是字符串,從而減少了潛在的安全漏洞。
因此,當執行復雜的子進程操作、需要更多控制權和靈活性、以及考慮到安全性時,優先選擇subprocess模塊是更好的選擇。而對於簡單的命令執行需求或與操作系統相關的功能,os模塊可能更加適合。