HOWTO · Python

如何在 Python 中運行 Bash 腳本

學習如何使用 Python 執行 Bash 腳本,請參閱我們全面的指南。探索像 subprocess、os.system 和 subprocess.Popen 等方法,以高效運行 Bash 命令。通過將 Bash 腳本整合到 Python 應用程序中,提升您的自動化技能並簡化工作流程。

本頁內容

大多數需要在 Python 繼續執行前完成的 Bash 指令碼,都適合使用 subprocess.run。它能分開傳入引數、擷取 stdout 和 stderr,並檢查程序的 returncode。執行環境必須已安裝 Bash,且 Python 能找到它;因此,沒有 Bash 的系統無法使用這種方式。下方範例會自行建立指令碼檔案、將名稱作為引數傳入,並顯示擷取到的結果。

方法 1:使用 subprocess 模組

這個自包含範例會建立一個簡短的 Bash 指令碼、傳入一個引數,並擷取兩個輸出串流。Bash 指令碼會輸出問候訊息,接著由 Python 顯示擷取到的文字和程序狀態。範例中的問候文字與狀態標籤維持英文,因為程式碼和輸出區塊必須逐位元組保持不變。

import subprocess
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    script_path = Path(directory) / "greet.sh"
    script_path.write_text(
        '#!/usr/bin/env bash\nprintf "Hello, %s!\\n" "$1"\n',
        encoding="utf-8",
    )

    result = subprocess.run(
        ["bash", str(script_path), "Ada"],
        capture_output=True,
        text=True,
        check=False,
    )

print(result.stdout, end="")
print(f"Return code: {result.returncode}")
Hello, Ada!
Return code: 0

引數清單會將指令碼路徑和 Ada 保持為兩個獨立引數。使用預設值 shell=False 時,Python 會直接啟動 Bash,而不會要求 shell 剖析這份清單。這能避免 shell 剖析這些引數,但不代表任何情況都安全:請驗證輸入,並留意指令碼本身執行的操作。returncode 為零表示成功;非零值表示程序回報失敗。stderr 是用來輸出診斷資訊的獨立串流,其中出現文字本身不代表指令執行失敗。設定 capture_output=True 後,結果物件會提供 stdout 和 stderr。

方法 2:使用 os.system

只有在簡單指令就足夠、而且 Python 不需要擷取輸出時,才使用 os.system。它接受一個指令字串,透過系統 shell 執行,並將指令的輸出串流送到目前程序的標準串流。它的回傳值取決於系統,可能以編碼形式表示程序狀態,而不等於子程序的結束碼,因此不可把它當成可攜的直接結束碼。它不會提供可直接讀取 stdout 或 stderr 的結果物件。避免使用不受信任的輸入組成指令字串,因為 shell 剖析時可能會賦予輸入特殊意義。若需要分開傳入引數、擷取串流或可靠地檢查狀態,請優先使用 subprocess。

方法 3:使用 subprocess.Popen

如果需要更細緻地控制執行中的程序,例如管理程序生命週期或連接管線,請使用 subprocess.Popen。若要進行有限次的輸入與輸出交換,建立程序時請設定 stdin=subprocess.PIPE,再呼叫 communicate(input=...) 傳送輸入,並在程序結束後收集擷取到的串流。若沒有設定 stdin 管線,communicate(input=...) 就無法將資料傳給子程序。它不會即時傳送輸出。如果為兩個輸出串流都建立了管線,請使用 communicate() 將它們讀取完畢;若子程序的管線已填滿,卻仍等待子程序完成,可能會造成死結。若要即時串流,請妥善讀取並排空串流,不要把 communicate() 當成串流 API。管線的實際行為會因平台和程序而異。

結論

若 Bash 指令碼會執行至完成,通常使用 subprocess.run 最清楚:傳入清單、視需要擷取串流,並以 returncode 判斷是否成功。os.system 適合不需要處理輸出的簡單 shell 指令;需要明確控制程序或進行程序通訊時,則使用 subprocess.Popen。請確認 Python 程式執行的環境已安裝 Bash,且 Python 能找到 Bash 可執行檔;否則就無法啟動指令碼。

常見問題

  1. 從 Python 執行 Bash 指令碼,哪種方式最好?
    大多數會在 Python 繼續執行前結束的指令碼,都適合使用 subprocess.run。它支援引數清單、擷取輸出串流,並透過 returncode 提供程序狀態。

  2. 如何擷取指令碼的輸出?
    在 subprocess.run 中設定 capture_output=True 和 text=True,再從回傳物件讀取 stdout 和 stderr。使用 Popen 時,連接所需的管線並呼叫 communicate(),即可在程序完成後收集串流;這會收集輸出,而非即時串流。即使程序成功,stderr 仍可能包含診斷資訊,因此請用 returncode 檢查程序狀態。

  3. 傳入清單安全嗎?
    使用 shell=False 時,清單中的引數不會經過 shell 剖析。這能降低 shell 注入風險,但不代表所有指令碼或輸入都安全;請驗證輸入值,並考量指令碼實際執行的操作。

  4. 可以將引數傳給 Bash 指令碼嗎?
    可以。在 subprocess 的引數清單中,將每個引數分別放在指令碼路徑之後即可。os.system 接受的是單一指令字串,無法像這樣以結構化方式傳入引數。

  5. 如何提供輸入給指令碼?
    簡單且有限的輸入交換可使用 subprocess.run 的 input=...;若需要更細緻地控制程序,則使用搭配 stdin=subprocess.PIPE 的 Popen,並呼叫 communicate(input=...)。communicate() 會傳送輸入,並在子程序結束後收集程序串流。