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 "<stdin>", line 1, in <module> 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 <module> 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
的影響,之後使用時就不會錯亂了。
Top comments (0)