subprocess.run 的眉眉角角
subprocess.run 可以讓我們透過 Python 程式碼執行外部的程式,不過個別參數都會對實際的執行結果有影響,以下我們分別說明。 env 參數會影響傳遞的環境變數內容 env 參數會影響傳遞到子行程中的環境變數,以底下這個簡單的程式為例: import subprocess import os, sys print('-' * 20) for i, name in enumerate(os.environ): print(f'{i:02d}:{name}') if len(sys.argv) > 1: match sys.argv[1]: case 'empty': env = {} case 'none': env = None case _: k, v = sys.argv[1].split('=') env = {k: v} shell = len(sys.argv) > 2 and sys.argv[2] == 'shell' subprocess.run( args=['python', 'test_env.py'], env=env, shell=shell ) 如果以如下引數執行: python test_env.py none 這會傳入 None 給 subprocess.run 的 env 參數,這也是 env 參數的預設值,會將目前的環境變數全數傳遞給子行程,得到如下結果: -------------------- 00:AKASH_API_KEY ... 80:ZES_ENABLE_SYSMAN -------------------- 00:AKASH_API_KEY ... 80:ZES_ENABLE_SYSMAN 父行程和子行程的環境變數一模一樣。如果以如下引數執行程式: python test_env.py empty 由於這會傳入 {} 空的字典給 env 參數,所以子行程的環境變數是空的,什麼都沒有: -------------------- 00:AKASH_API_KEY ... 80:ZES_ENABLE_SYSMAN -------------------- 如果傳入客製的環境變數內容,例如: python test_env.py TEST=hello 就會看到雖然父行程的環境變數都不會傳入子行程,但會傳遞客製的環境變數: 80:ZES_ENABLE_SYSMAN -------------------- 00:AKASH_API_KEY ... -------------------- 00:TEST shell 參數的影響 如果設定 shell 參數為 True,表示要先以子行程啟動系統預設的命令解譯器,由該命令解譯器來執行傳給 args 參數的指令。 以下仍以前一小節的範例說明。我們重複上述的測試,首先是採用 env 預設值 None,但是在命令行加上 shell 引數: python test_env.py none shell 在 Windows 上會依據環境變數 COMSPEC 的設定執行預設解譯器,沒有修改的話就是 cmd,它會額外新增一個 PROMPT 的環境變數,用來制訂輸入提示符號的格式,所以你會看到以子行程比父行程多了一個環境變數: -------------------- 00:AKASH_API_KEY ... 52:PROGRAMW6432 53:PSMODULEPATH ... 80:ZES_ENABLE_SYSMAN -------------------- 00:AKASH_API_KEY ... 52:PROGRAMW6432 53:PROMPT 54:PSMODULEPATH ... 81:ZES_ENABLE_SYSMAN 再來測試傳遞空的環境變數給子行程: python test_env.py empty shell 由於子行程的環境變數是空的,所以命令解譯程式並沒有 PATH 環境變數的資訊,所以它不知道 python 指令該去哪裡找到可執行檔來執行,因而無法執行而顯示錯誤訊息: -------------------- 00:AKASH_API_KEY ... 80:ZES_ENABLE_SYSMAN 'python' 不是內部或外部命令、可執行的程式或批次檔。 同樣的道理,如果傳遞客製的環境變數: python test_env.py TEST=hello shell 也一樣沒有 PATH 環境變數,還是無法執行: -------------------- 00:AKASH_API_KEY ... 80:ZES_ENABLE_SYSMAN 'python' 不是內部或外部命令、可執行的程式或批次檔。 如果傳遞客製的 PATH 環境變數,涵蓋 python 直譯器的路徑: python test_env.py PATH=C:\users\meebo\code\python\test_py3.13\.venv\scripts shell 就可以正常執行了: -------------------- 00:AKASH_API_KEY ... 10:COMSPEC ... 34:PATH 35:PATHEXT ... 80:ZES_ENABLE_SYSMAN -------------------- 00:COMSPEC 01:PATH 02:PATHEXT 03:PROMPT 除了我們傳遞過去的 PATH 以外,其他三個環境變數都是 cmd 會自動建立的環境變數。 不同平台的差異 雖然 Python 已經為不同平台提供了一致的介面,但是實際上不同平台還是存在差異,以下再詳細說明。 shell=True 時 args 的差異 在 Linux 平台上,如果 shell 設為 True,那麼 args 必須傳入字串,內含完整的命令行,不能拆開成指令與引數的串列,舉例來說,以下是 Windows 平台的例子: >>> subprocess.run( ... args='dir test_env.py', ... shell=True ... ) 確認結果正確: 磁碟區 C 中的磁碟是 Book 13 磁碟區序號: 8482-E7D5 C:\Users\meebo\code\python\test_py3.13 的目錄 2025/04/05 下午 04:22 635 test_env.py 1 個檔案 635 位元組 0 個目錄 75,697,201,152 位元組可用 CompletedProcess(args='dir test_env.py', returncode=0) 改成傳入字串串列: >>> subprocess.run( ... args=['dir', 'test_env.py'], ... shell=True ... ) 一樣可以正常運作: 磁碟區 C 中的磁碟是 Book 13 磁碟區序號: 8482-E7D5 C:\Users\meebo\code\python\test_py3.13 的目錄 2025/04/05 下午 04:22 635 test_env.py 1 個檔案 635 位元組 0 個目錄 75,695,497,216 位元組可用 CompletedProcess(args=['dir', 'test_env.py'], returncode=0) >>> 不論是傳入單一字串或是字串串列,都可以正確運作。但如果是在 Linux 平台上: >>> subprocess.run( ... 'ls -l test_env.py', ... shell=True ... ) 可以看到指定檔案的詳細資訊: -rwxrwxrwx 1 meebox meebox 635 Apr 5 16:22 test_env.py CompletedProcess(args='ls -l test_env.py', returncode=0) 但如果改成字串串列: >>> subprocess.run( ... args=['ls', '-l', 'test_env.py'], ... shell=True ... ) 執行結果就不對了: asyncio_openai.py package-lock.json test2.py test_FAISS.py my_faiss_db __pycache__ test_copilot.py test.py package.json spotify_play.py test_env.py CompletedProcess(args=['ls', '-l', 'test_env.py'], returncode=0) >>> 你可以看到傳入整個命令行的字串可以正確運作,但是若拆成字串串列,命令解譯程式只會執行第一個元素,也就是 'ls',結果變成是顯示當前資料夾下的檔案,而不是顯示指定檔案的詳細資訊了。 以剛剛的測試程式為例,如果以下列方式執行: python test_env.py none shell 就會看到程式停在直譯器的輸入提示: -------------------- 00:SHELL ... 30:TERM_PROGRAM 31:_ Python 3.13.2 (main, Mar 17 2025, 21:02:54) [Clang 20.1.0 ] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 不會結束,這是因為命令解譯程式只把串列中的第一個元素 'python' 當成命令行,所以就會進入 REPL 等待使用者輸入程式。在 Windows 上並不會發生同樣的問題。 把程式碼改成以下這樣: import subprocess import os, sys print('-' * 20) for i, name in enumerate(os.environ): print(f'{i:02d}:{name}') if len(sys.argv) > 1: match sys.argv[1]: case 'empty': env = {} case 'none': env = None case _: k, v = sys.argv[1].split

subprocess.run
可以讓我們透過 Python 程式碼執行外部的程式,不過個別參數都會對實際的執行結果有影響,以下我們分別說明。
env 參數會影響傳遞的環境變數內容
env
參數會影響傳遞到子行程中的環境變數,以底下這個簡單的程式為例:
import subprocess
import os, sys
print('-' * 20)
for i, name in enumerate(os.environ):
print(f'{i:02d}:{name}')
if len(sys.argv) > 1:
match sys.argv[1]:
case 'empty':
env = {}
case 'none':
env = None
case _:
k, v = sys.argv[1].split('=')
env = {k: v}
shell = len(sys.argv) > 2 and sys.argv[2] == 'shell'
subprocess.run(
args=['python', 'test_env.py'],
env=env,
shell=shell
)
如果以如下引數執行:
python test_env.py none
這會傳入 None
給 subprocess.run
的 env
參數,這也是 env 參數的預設值,會將目前的環境變數全數傳遞給子行程,得到如下結果:
--------------------
00:AKASH_API_KEY
...
80:ZES_ENABLE_SYSMAN
--------------------
00:AKASH_API_KEY
...
80:ZES_ENABLE_SYSMAN
父行程和子行程的環境變數一模一樣。如果以如下引數執行程式:
python test_env.py empty
由於這會傳入 {}
空的字典給 env
參數,所以子行程的環境變數是空的,什麼都沒有:
--------------------
00:AKASH_API_KEY
...
80:ZES_ENABLE_SYSMAN
--------------------
如果傳入客製的環境變數內容,例如:
python test_env.py TEST=hello
就會看到雖然父行程的環境變數都不會傳入子行程,但會傳遞客製的環境變數:
80:ZES_ENABLE_SYSMAN
--------------------
00:AKASH_API_KEY
...
--------------------
00:TEST
shell 參數的影響
如果設定 shell
參數為 True
,表示要先以子行程啟動系統預設的命令解譯器,由該命令解譯器來執行傳給 args
參數的指令。
以下仍以前一小節的範例說明。我們重複上述的測試,首先是採用 env
預設值 None
,但是在命令行加上 shell 引數:
python test_env.py none shell
在 Windows 上會依據環境變數 COMSPEC
的設定執行預設解譯器,沒有修改的話就是 cmd
,它會額外新增一個 PROMPT
的環境變數,用來制訂輸入提示符號的格式,所以你會看到以子行程比父行程多了一個環境變數:
--------------------
00:AKASH_API_KEY
...
52:PROGRAMW6432
53:PSMODULEPATH
...
80:ZES_ENABLE_SYSMAN
--------------------
00:AKASH_API_KEY
...
52:PROGRAMW6432
53:PROMPT
54:PSMODULEPATH
...
81:ZES_ENABLE_SYSMAN
再來測試傳遞空的環境變數給子行程:
python test_env.py empty shell
由於子行程的環境變數是空的,所以命令解譯程式並沒有 PATH
環境變數的資訊,所以它不知道 python
指令該去哪裡找到可執行檔來執行,因而無法執行而顯示錯誤訊息:
--------------------
00:AKASH_API_KEY
...
80:ZES_ENABLE_SYSMAN
'python' 不是內部或外部命令、可執行的程式或批次檔。
同樣的道理,如果傳遞客製的環境變數:
python test_env.py TEST=hello shell
也一樣沒有 PATH
環境變數,還是無法執行:
--------------------
00:AKASH_API_KEY
...
80:ZES_ENABLE_SYSMAN
'python' 不是內部或外部命令、可執行的程式或批次檔。
如果傳遞客製的 PATH
環境變數,涵蓋 python 直譯器的路徑:
python test_env.py PATH=C:\users\meebo\code\python\test_py3.13\.venv\scripts shell
就可以正常執行了:
--------------------
00:AKASH_API_KEY
...
10:COMSPEC
...
34:PATH
35:PATHEXT
...
80:ZES_ENABLE_SYSMAN
--------------------
00:COMSPEC
01:PATH
02:PATHEXT
03:PROMPT
除了我們傳遞過去的 PATH
以外,其他三個環境變數都是 cmd
會自動建立的環境變數。
不同平台的差異
雖然 Python 已經為不同平台提供了一致的介面,但是實際上不同平台還是存在差異,以下再詳細說明。
shell=True 時 args 的差異
在 Linux 平台上,如果 shell
設為 True
,那麼 args
必須傳入字串,內含完整的命令行,不能拆開成指令與引數的串列,舉例來說,以下是 Windows 平台的例子:
>>> subprocess.run(
... args='dir test_env.py',
... shell=True
... )
確認結果正確:
磁碟區 C 中的磁碟是 Book 13
磁碟區序號: 8482-E7D5
C:\Users\meebo\code\python\test_py3.13 的目錄
2025/04/05 下午 04:22 635 test_env.py
1 個檔案 635 位元組
0 個目錄 75,697,201,152 位元組可用
CompletedProcess(args='dir test_env.py', returncode=0)
改成傳入字串串列:
>>> subprocess.run(
... args=['dir', 'test_env.py'],
... shell=True
... )
一樣可以正常運作:
磁碟區 C 中的磁碟是 Book 13
磁碟區序號: 8482-E7D5
C:\Users\meebo\code\python\test_py3.13 的目錄
2025/04/05 下午 04:22 635 test_env.py
1 個檔案 635 位元組
0 個目錄 75,695,497,216 位元組可用
CompletedProcess(args=['dir', 'test_env.py'], returncode=0)
>>>
不論是傳入單一字串或是字串串列,都可以正確運作。但如果是在 Linux 平台上:
>>> subprocess.run(
... 'ls -l test_env.py',
... shell=True
... )
可以看到指定檔案的詳細資訊:
-rwxrwxrwx 1 meebox meebox 635 Apr 5 16:22 test_env.py
CompletedProcess(args='ls -l test_env.py', returncode=0)
但如果改成字串串列:
>>> subprocess.run(
... args=['ls', '-l', 'test_env.py'],
... shell=True
... )
執行結果就不對了:
asyncio_openai.py package-lock.json test2.py test_FAISS.py
my_faiss_db __pycache__ test_copilot.py test.py
package.json spotify_play.py test_env.py
CompletedProcess(args=['ls', '-l', 'test_env.py'], returncode=0)
>>>
你可以看到傳入整個命令行的字串可以正確運作,但是若拆成字串串列,命令解譯程式只會執行第一個元素,也就是 'ls',結果變成是顯示當前資料夾下的檔案,而不是顯示指定檔案的詳細資訊了。
以剛剛的測試程式為例,如果以下列方式執行:
python test_env.py none shell
就會看到程式停在直譯器的輸入提示:
--------------------
00:SHELL
...
30:TERM_PROGRAM
31:_
Python 3.13.2 (main, Mar 17 2025, 21:02:54) [Clang 20.1.0 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
不會結束,這是因為命令解譯程式只把串列中的第一個元素 'python' 當成命令行,所以就會進入 REPL 等待使用者輸入程式。在 Windows 上並不會發生同樣的問題。
把程式碼改成以下這樣:
import subprocess
import os, sys
print('-' * 20)
for i, name in enumerate(os.environ):
print(f'{i:02d}:{name}')
if len(sys.argv) > 1:
match sys.argv[1]:
case 'empty':
env = {}
case 'none':
env = None
case _:
k, v = sys.argv[1].split('=')
env = {k: v}
shell = len(sys.argv) > 2 and sys.argv[2] == 'shell'
args = 'python test_env.py' if shell else ['python', 'test_env.py']
subprocess.run(
args=args,
# args='which python3',
env=env,
shell=shell
)
在 Linux 上就可以正確執行了:
--------------------
00:SHELL
...
31:_
--------------------
00:WARP_HONOR_PS1
...
31:WSLENV
env 會影響搜尋可執行檔
在 Windows 上,subprocess.run
底層是 CreateProcess,會以當前的環境設定找尋 args
中指定的可執行檔,但是在 Linux 上底層是 evecve,是以 env
所設定的環境搜尋可執行檔。以下是 Windows 的例子:
>>> subprocess.run(
... args='python',
... )
可以正確看到 python REPL 的輸入提示:
Python 3.13.1 (main, Dec 19 2024, 14:38:48) [MSC v.1942 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
CompletedProcess(args='python', returncode=0)
以上可以確認當前環境可以執行 python。接著傳遞空的環境變數:
>>> subprocess.run(
... args='python',
... env={}
... )
同樣可以看到 python REPL 的輸入提示:
Python 3.13.1 (main, Dec 19 2024, 14:38:48) [MSC v.1942 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
CompletedProcess(args='python', returncode=0)
由於當前的環境沒有改變,仍然可以執行 python。但如果傳入 True
給 shell
:
>>> subprocess.run(
... args='python',
... env={},
... shell=True
... )
會看到錯誤訊息:
'python' 不是內部或外部命令、可執行的程式或批次檔。
CompletedProcess(args='python', returncode=1)
因為是先執行 cmd,這是環境已經變成 env
設定的空環境,所以沒有 PATH
的資訊,找不到 python,因此會看到 cmd 顯示的錯誤訊息。
換成 Linux 環境,首先確認可以執行 python:
>>> import subprocess
>>> subprocess.run(
... 'python',
... )
執行後會看到 python REPL 的輸入提示:
Python 3.13.2 (main, Mar 17 2025, 21:02:54) [Clang 20.1.0 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
CompletedProcess(args='python', returncode=0)
接著一樣傳遞空的環境給 env
參數:
>>> subprocess.run(
... 'python',
... env={}
... )
執行會出現如下錯誤:
Traceback (most recent call last):
File "", line 1, in
File "/home/meebox/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/subprocess.py", line 556, in run
with Popen(*popenargs, **kwargs) as process:
~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/home/meebox/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/subprocess.py", line 1038, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pass_fds, cwd, env,
^^^^^^^^^^^^^^^^^^^
...<5 lines>...
gid, gids, uid, umask,
^^^^^^^^^^^^^^^^^^^^^^
start_new_session, process_group)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/meebox/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/subprocess.py", line 1974, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'python'
這時候因為是依照 env
參數的設定尋找可執行檔,但是 env
是空的,所以也沒有路徑資訊可參考,因此就無法找到 python 執行檔了。
之前測試的範例,如果拿到 Linux 下,除了 env
使用預設的 None
以外,執行都會出錯,例如:
python test_env.py empty
執行結果如下:
--------------------
00:SHELL
...
30:TERM_PROGRAM
31:_
Traceback (most recent call last):
File "/mnt/c/Users/meebo/code/python/test_py3.13/test_env.py", line 20, in
subprocess.run(
~~~~~~~~~~~~~~^
args=args,
^^^^^^^^^^
...<2 lines>...
shell=shell
^^^^^^^^^^^
)
^
File "/home/meebox/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/subprocess.py", line 556, in run
with Popen(*popenargs, **kwargs) as process:
~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/home/meebox/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/subprocess.py", line 1038, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pass_fds, cwd, env,
^^^^^^^^^^^^^^^^^^^
...<5 lines>...
gid, gids, uid, umask,
^^^^^^^^^^^^^^^^^^^^^^
start_new_session, process_group)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/meebox/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/subprocess.py", line 1974, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'python'
這就是因為設定的環境中並沒有 PATH
,所以都無法找到 python 可執行檔執行。
因此,使用 subprocess.run
時,最好就是使用完整的路徑或是相對路徑傳入 args
參數,不然就可能會發生找不到可執行檔而無法正常執行的狀況。
結語
以上的展示應該可以清楚說明 env
和 shell
的影響,之後使用時就不會錯亂了。