Python / subprocess === ###### tags: `Python` ###### tags: `Python`, `subprocess` <br> [TOC] <br> ## subprocess.run() ### `check` 參數說明(以 `cat xxx.txt` 為例) 以下程式碼會嘗試顯示一個不存在的檔案 `xxx.txt` 的內容: ```python import subprocess subprocess.run(['cat', 'xxx.txt'], check=False) ``` - **DEMO** ![](https://hackmd.io/_uploads/S1FSZHLAyx.png) --- #### ✅ `check=False`(預設值) - 即使 `cat xxx.txt` 指令失敗(例如檔案不存在),程式**不會拋出錯誤**。 - 若你想知道有沒有錯誤發生,需要自己檢查 `returncode`。 範例如下: ```python= import subprocess result = subprocess.run(['cat', 'xxx.txt'], check=False) print(result.returncode) # 通常會是 1 或 2,表示錯誤 ``` --- #### ⚠️ `check=True` - 如果 `cat xxx.txt` 指令失敗,`subprocess.run()` 會**拋出例外** `subprocess.CalledProcessError`。 ![](https://hackmd.io/_uploads/H1EjgHLCye.png) - 程式會中斷,除非你用 try-except 捕捉錯誤。 範例如下: ```python= import subprocess try: subprocess.run(['cat', 'xxx.txt'], check=True) except subprocess.CalledProcessError as e: print("命令失敗!", e) ``` - **DEMO** ![](https://hackmd.io/_uploads/SyWTZSICyl.png) --- ### 小結:`check=True` vs `check=False` | `check=False`(預設)| `check=True` | |--------------------|------------| | 指令失敗時,不會拋出錯誤 | 指令失敗時,會拋出例外 | | 需要自行檢查 `returncode` | 錯誤會中斷流程,適合關鍵性操作 | | 程式繼續執行 | 程式會進入 except 或終止執行 | <br> ## 參考資料 - [[系統互動篇] 如何呼叫系統指令,並讀取結果](https://github.com/tsungjung411/python-study/wiki/%5B系統互動篇%5D-如何呼叫系統指令,並讀取結果) <br> ## 討論 ### `shell=True` vs `shell=False` - ### Question ``` # Run a simple shell command as a string executor.run("echo Hello World") # Run a shell command provided as a list of arguments executor.run(["ls", "-l"]) ``` 都是 shell command, 為何要使用 shell=True/False? 真正含意為何? - ### gpt-4o (2025/04/24) ### 🔍 差異簡介:shell=True vs shell=False | 特性 | `shell=True` | `shell=False` | |------|--------------|---------------| | 執行方式 | 指令會由 **shell 解譯器**(如 `/bin/sh`)來執行 | 直接執行可執行檔 | | 支援 shell 語法(如管線、環境變數展開) | ✅ 支援 | ❌ 不支援 | | 輸入格式 | 字串 | **列表**(推薦) | | 安全性 | ❌ 易受 command injection 攻擊 | ✅ 安全(建議用於外部輸入) | | 範例 | `"ls -la \| grep py"` | `["ls", "-la"]` | --- ### ✅ 重點說明 #### 🔸 為何有時要 `shell=True`? 因為你想要執行的是一個 shell command,而不是單純一個執行檔。舉幾個例子: ``` subprocess.run("echo $HOME", shell=True) # ok subprocess.run("ls | grep py", shell=True) # ok ``` 這些包含了 **shell 語法**(例如 `$HOME`, `|`)的指令,必須透過 shell 來解譯。 --- #### 🔸 為何推薦用 `shell=False`(預設)? 當你用 `["ls", "-l"]` 這種 **list** 格式,並且 `shell=False`,它會: - 不經過 shell - 直接執行 `ls` 程式 - 更安全,不會受到指令注入攻擊的影響 這是最常見、最安全的執行方式,特別是你要用外部輸入(如用戶指定的檔案名稱、指令等)時。 --- ### ✅ 實際對比範例 ```python # 使用 shell=True,才會經由 shell 解譯 subprocess.run("echo $HOME", shell=True) # 使用 shell=False,會當成整個字串去找執行檔,導致錯誤 subprocess.run(["echo $HOME"], shell=False) # ERROR # FileNotFoundError: [Errno 2] No such file or directory: 'echo $HOME' # 正確方式,不使用 shell 語法就可以 shell=False subprocess.run(["echo", "hello"], shell=False) # OK ``` --- ### 🤔 什麼情況選 shell=True? 1. 需要使用 shell 語法(如管線 `|`、重導向 `>`、環境變數 `$HOME`) 2. 執行 script command(如 `bash -c "..."`) 3. 不介意 command injection(例如寫死的指令) ✅ **但只要你有來自使用者輸入的字串要丟進去執行,就應該堅持 shell=False!** --- ### ✅ 總結:用法對照 | 情境 | 建議用法 | |------|-----------| | 直接執行 `ls -la` | `["ls", "-la"]` + `shell=False` | | 執行 `ls | grep py` | `"ls | grep py"` + `shell=True` | | 有管線、環境變數、萬用字元 | `shell=True` | | 有 user 輸入參與 | `shell=False` 並用 list | <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}