Pythonで外部コマンドや他のプログラムを実行する際、かつてはos.systemやos.popenが広く使われてきました。
しかし、現代のPythonプログラミングにおいて、これらは「過去の遺物」となりつつあります。
現在、外部プロセスを生成し、その実行結果を制御するための標準的かつ最も推奨される手法はsubprocessモジュールのrun関数を使用することです。
subprocess.runは、コマンドの実行から標準出力の取得、エラー処理、タイムアウト設定までをひとつの関数で完結できる非常に強力なAPIです。
本記事では、2026年現在の開発現場でも通用するsubprocess.runの正しい使い方と、安全にコマンドを制御するためのベストプラクティスを詳しく解説します。
subprocess.runの基本構造
subprocess.runは、Python 3.5で導入されて以来、外部コマンド実行のデファクトスタンダードとなりました。
この関数の最大の特徴は、プロセスの終了を待機し、その結果を「CompletedProcess」オブジェクトとして返すという点にあります。
まずは、最もシンプルな実行例を見てみましょう。
基本的な実行例
import subprocess
# 最もシンプルなコマンド実行
# リスト形式でコマンドと引数を渡すのが推奨
result = subprocess.run(["echo", "Hello, Python Subprocess!"])
print(f"終了コード: {result.returncode}")
Hello, Python Subprocess!
終了コード: 0
この例では、echoコマンドに引数を渡して実行しています。
戻り値であるresultには実行結果に関する情報が格納されており、returncode属性を参照することで、コマンドが成功したか(通常は0)を確認できます。
引数をリスト形式で渡すべき理由
subprocess.runに渡すコマンドは、文字列ではなくリスト形式(例: [“ls”, “-l”])で渡すことが強く推奨されます。
これには主に2つの理由があります。
- OSによるパースの違いを回避: スペースを含むファイル名などを扱う際、文字列で渡すとシェルが意図しない場所で分割してしまうことがありますが、リスト形式なら正確に引数を渡せます。
- セキュリティ(シェルインジェクション対策): ユーザー入力をコマンドに含める場合、文字列結合で行うと悪意のあるコマンドを埋め込まれるリスクがありますが、リスト形式であればそれらは単なる「引数」として扱われるため安全です。
標準出力と標準エラー出力の取得
単にコマンドを実行するだけでなく、その実行結果(テキスト)をPython変数として取得したいケースは多々あります。
デフォルトでは、コマンドの出力はPythonのプログラムとは別に「標準出力」としてコンソールに直接表示されてしまいます。
capture_outputによる出力のキャプチャ
出力を変数に格納するには、capture_output=Trueという引数を使用します。
import subprocess
# capture_output=True で出力をキャプチャする
result = subprocess.run(["echo", "Python出力テスト"], capture_output=True)
# 出力結果は bytes 型で格納されている
print(f"標準出力(bytes): {result.stdout}")
標準出力(bytes): b'Python\xe5\x87\xba\xe5\x8a\x9b\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88\n'
text=Trueによる文字列としての取得
デフォルトでは出力はbytes型として取得されますが、日本語などを扱う場合はtext=Trueを指定して文字列(str型)として取得するのが一般的です。
import subprocess
# text=True (旧 universal_newlines=True) を指定
result = subprocess.run(["echo", "こんにちは!"], capture_output=True, text=True)
print(f"標準出力(str): {result.stdout.strip()}")
標準出力(str): こんにちは!
text=Trueを設定すると、OSのデフォルトエンコーディングに基づいてデコードが行われ、改行コードも適切に処理されます。
| 引数 | 型 | 説明 |
|---|---|---|
capture\_output | bool | Trueにすると標準出力と標準エラーをキャプチャする |
text | bool | Trueにすると出力をbytesではなくstrとして扱う |
stdout | subprocess.PIPE | 個別に標準出力を制御する場合に使用 |
stderr | subprocess.PIPE | 個別に標準エラーを制御する場合に使用 |
エラー処理と例外送出
外部コマンドが常に成功するとは限りません。
ファイルが存在しない、権限が足りない、ネットワークエラーが発生したなど、失敗した際の処理を適切に記述する必要があります。
check=Trueによる自動エラーチェック
通常、コマンドが失敗(終了コードが0以外)しても、Pythonはそのまま処理を続行します。
しかし、check=Trueを渡すことで、エラーが発生した際に自動的に例外「CalledProcessError」を発生させることができます。
import subprocess
try:
# 存在しないコマンドや、失敗するコマンドを実行
subprocess.run(["ls", "non_existent_file"], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print(f"コマンドが失敗しました。終了コード: {e.returncode}")
print(f"エラー内容: {e.stderr}")
コマンドが失敗しました。終了コード: 2
エラー内容: ls: non_existent_file: No such file or directory
このようにtry-except構文と組み合わせることで、エラー発生時のリカバリ処理を安全に記述できます。
予期せぬエラーを見逃さないために、原則としてcheck=Trueを常に指定することを推奨します。
タイムアウトの設定
外部コマンドがフリーズしたり、予想以上に時間がかかったりする場合、Pythonプログラム全体が停止してしまうリスクがあります。
これを防ぐためにtimeout引数を利用します。
import subprocess
try:
# 5秒かかる処理を2秒でタイムアウトさせる例
subprocess.run(["sleep", "5"], timeout=2)
except subprocess.TimeoutExpired as e:
print("処理がタイムアウトしました。")
処理がタイムアウトしました。
TimeoutExpired例外が発生した際も、それまでにキャプチャされた出力があれば例外オブジェクトから参照することが可能です。
バッチ処理や自動化ツールを作成する際は、必ずこのタイムアウトを設定するようにしましょう。
shell=Trueの危険性と使い所
subprocess.runにはshell=Trueという引数があります。
これを使用すると、コマンドを直接実行するのではなく、OSの「シェル」を経由して実行します。
shell=Trueのリスク
# 危険な例: ユーザー入力をそのまま渡すとシェルインジェクションの恐れがある
user_input = "; rm -rf /"
subprocess.run(f"echo {user_input}", shell=True)
shell=Trueを使用すると、パイプ(|)やリダイレクト(>)、環境変数の展開などのシェル機能が使えて便利ですが、外部からの入力をそのまま渡すと、意図しないコマンドを実行される脆弱性につながります。
shell=Trueが必要なケース
どうしてもパイプを多用した複雑なワンライナーを実行したい場合のみ、十分に注意した上で使用します。
ただし、可能な限りPython側でロジックを組むか、後述するパイプラインの構築を検討すべきです。
高度な利用シーン:ディレクトリ指定と環境変数
特定のディレクトリでコマンドを実行したい場合や、特定の環境変数を渡したい場合もsubprocess.runで完結できます。
カレントディレクトリの変更 (cwd)
cwd引数を使うと、Pythonプログラム自体のディレクトリを移動させることなく、外部コマンドの実行場所を指定できます。
import subprocess
# /tmp ディレクトリで 'pwd' コマンドを実行
subprocess.run(["pwd"], cwd="/tmp")
環境変数の追加 (env)
既存の環境変数に加えて、特定の変数をプロセスに渡したい場合は、辞書形式でenvを指定します。
import os
import subprocess
# 現在の環境変数をコピーし、新しい変数を追加
my_env = os.environ.copy()
my_env["MY_APP_MODE"] = "DEBUG"
subprocess.run(["printenv", "MY_APP_MODE"], env=my_env)
DEBUG
このように、os.environ.copy()を使って現在の環境を引き継ぎつつ、必要な値だけを上書き・追加するのが安全な手法です。
Popenとrunの使い分け
subprocessモジュールには、runの他にも低レベルなAPIであるPopenクラスが存在します。
subprocess.runを使うべき時
- コマンドの終了を待ってから次の処理に進みたい。
- 実行結果(戻り値や出力テキスト)を簡単に取得したい。
- シンプルなスクリプトやバッチ処理。
subprocess.Popenを使うべき時
- プロセスを実行しながら、リアルタイムに出力を読み取りたい。
- プロセスが終了する前に別の処理を並行して行いたい。
- 高度なパイプライン(複数のコマンドを動的につなぐ)を構築したい。
9割以上のケースではsubprocess.runで十分であり、コードの可読性も高くなります。
まずはrunを検討し、それでは実現不可能な特殊な要件がある場合にのみPopenを選択しましょう。
実践的な活用例:Gitコマンドの操作
開発現場でよくある「PythonからGit操作を行う」スクリプトを例に、これまでの知識を統合してみます。
import subprocess
def get_git_branch():
try:
# git branch コマンドを実行
result = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True,
text=True,
check=True
)
# 出力結果の改行などを削除して返す
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Gitコマンドの実行に失敗しました: {e.stderr}")
return None
current_branch = get_git_branch()
if current_branch:
print(f"現在のブランチは {current_branch} です。")
このように、check=True、capture_output=True、text=Trueの3つをセットで使うのが、現代のPythonにおける外部コマンド実行の黄金パターンです。
パフォーマンスと注意点
外部プロセスの起動は、Python内部の関数呼び出しに比べて非常にコストがかかります。
ループの中で数千回、数万回とsubprocess.runを呼び出すような設計は避けましょう。
もし大量の処理を外部コマンドで行いたい場合は、
- 一回の呼び出しで複数の引数を渡す。
- Pythonの標準ライブラリで代用できないか検討する(例: ファイル操作なら
shutilやpathlibを使う)。 - 非同期処理が必要な場合は
asyncioモジュールのサブプロセス機能を利用する。
といったアプローチが有効です。
まとめ
Pythonのsubprocess.runは、シンプルさと強力な機能を兼ね備えた、外部コマンド実行の決定版です。
今回の重要ポイントを振り返ります。
- 引数は必ずリスト形式で渡す (セキュリティと正確性のため)。
- 結果を取得する際は capture_output=True と text=True を活用する。
- エラーを検知するために check=True を指定する。
- 無限ループやフリーズを防ぐために timeout を設定する。
- shell=True の使用は最小限にとどめ、信頼できない入力を渡さない。
これらのルールを守るだけで、Pythonから外部ツールを安全かつ効率的に制御できるようになります。
OSに依存するコマンドを実行する場合は、WindowsやLinuxでの挙動の差に注意しつつ、適切なエラーハンドリングを実装しましょう。
最新のPython環境においても、OSとの対話は自動化の核となる技術です。
subprocess.runを使いこなし、堅牢なスクリプト作成に役立ててください。
