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**

---
#### ✅ `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`。

- 程式會中斷,除非你用 try-except 捕捉錯誤。
範例如下:
```python=
import subprocess
try:
subprocess.run(['cat', 'xxx.txt'], check=True)
except subprocess.CalledProcessError as e:
print("命令失敗!", e)
```
- **DEMO**

---
### 小結:`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 %}