結論:引数をリストで渡し、check・timeout・出力の扱いを明示する
外部コマンドを自動処理から呼ぶなら、subprocess.runへ引数をリストで渡し、失敗を検出するcheck、待ち時間を制限するtimeout、出力を受け取る方法を決める。戻ってきたというだけで成功とみなさず、終了コードと標準エラー出力を確認できる形にする。コマンド文字列を手作業で結合してshellへ渡す方法は、不要なら避けたい。
シェルを介さない通常のリスト指定では、空白やセミコロンを含む値も一つの引数として渡せる。実行ファイルと引数を分離できるので、引用符の組み立てや意図しないシェル展開の問題を減らせる。ただし、呼ばれるプログラム自身がオプションや入力をどう解釈するかは別であり、リストにすれば任意のコマンドが安全になるという意味ではない。
そのまま動かせる例
以下は外部ツールを用意せず、実行中と同じPythonを子プロセスとして呼ぶ。成功、終了コード7での失敗、時間切れの3ケースを独立に確認する。標準ライブラリだけで動き、呼ぶコードはこの例の中で管理している短いPython命令だけである。任意のユーザー入力をコードとして実行する例ではない。
sys.executableを使うので、PATH上で別のpythonが選ばれる問題を避けやすい。成功例のhello; worldはそのまま一つの引数として届き、セミコロンで別コマンドには分かれない。失敗と時間切れは意図的な例で、それぞれ対応する例外が発生することまで確かめている。ファイルや外部サービスへの書き込みは行わない。
import subprocess
import sys
ok = subprocess.run(
[sys.executable, "-c", "import sys; print(sys.argv[1])", "hello; world"],
check=True, capture_output=True, text=True, encoding="utf-8", timeout=3)
assert ok.stdout == "hello; world\n" and ok.returncode == 0
print("success:", ok.stdout.strip())
try:
subprocess.run(
[sys.executable, "-c", "import sys; print('bad input', file=sys.stderr); sys.exit(7)"],
check=True, capture_output=True, text=True, encoding="utf-8", timeout=3)
except subprocess.CalledProcessError as exc:
assert exc.returncode == 7 and exc.stderr.strip() == "bad input"
print("failure code:", exc.returncode)
print("failure stderr:", exc.stderr.strip())
else:
raise AssertionError("expected command failure")
try:
subprocess.run([sys.executable, "-c", "import time; time.sleep(0.5)"],
check=True, capture_output=True, text=True, timeout=0.03)
except subprocess.TimeoutExpired:
print("timeout: TimeoutExpired")
else:
raise AssertionError("expected timeout")
実行結果
success: hello; world
failure code: 7
failure stderr: bad input
timeout: TimeoutExpired
終了コードと例外を対応させる
check=Trueでは、終了コードが0以外ならCalledProcessErrorが発生する。そこからreturncode、stdout、stderrを調べられる。例では終了コード7とbad inputを確認している。コマンドが見つからない場合のFileNotFoundErrorなど、そもそも起動できなかった問題はこの例外とは別なので、原因を同じ「処理失敗」へまとめすぎないようにする。
check=Falseで戻り値を調べる方式も使えるが、その場合は呼び出し側が終了コードを必ず判断する。標準出力が空だから失敗、標準エラーへ何か出たから失敗、という単純な判断はコマンドごとの仕様次第である。成功しても警告をstderrへ出すツールがあるし、終了コード0でも求める出力ファイルが正しいとは限らない。必要な成果物も検証しよう。
文字コードと大量出力
capture_output=Trueは標準出力と標準エラー出力を別々に受け取る。text=Trueとencodingを指定すると文字列として扱えるが、実際のコマンドが出す文字コードに合わせる必要がある。文字コード不明のバイナリ出力なら、無理に文字列へ変換せずbytesとして受ける。成功と失敗の出力を分けて残すと、後から原因を確認しやすい。
capture_outputは出力全体をメモリへためるので、巨大なログやデータを出すコマンドには向かない場合がある。ファイルへ直接流す、必要な範囲だけ処理するなど別の方法を検討する。認証情報や秘密の値が引数・出力へ含まれる場合は、例外をそのまま丸ごとログへ書くことにも注意したい。調査に必要な情報と公開してよい情報を分ける必要がある。
timeoutは子の派生プロセスまで万能ではない
runのtimeoutに達すると、直接起動した子プロセスを終了させて待ち合わせたうえでTimeoutExpiredを送出する。ただしプロセス生成の段階自体が常に中断できるわけではなく、指定秒数ぴったりで呼び出し全体が戻る保証ではない。また子がさらに起動したプロセス群まで一律に片付ける仕組みではないので、複雑な外部ツールには別の終了管理が必要になる。
この例の子は孫プロセスを作らないため、単純なtimeoutの挙動を確認できる。実際のコマンドでは作業フォルダーcwd、渡す環境変数env、入力ファイル、保存先も明示すると再現しやすい。Windowsのバッチファイルなどにはシェル解釈に関する追加の注意もある。今回はLinuxの制御されたPythonコマンドで検証しており、異なるOSや未知のツールの挙動まで保証するものではない。
確認環境と参考資料
例はLinux・CPython 3.12.14で実行した。掲載した出力はこの環境での結果である。公式資料のstable版や最新版は更新されるため、手元のバージョンと対応する仕様も確認してほしい。
- Python公式:subprocess.run(2026年10月2日参照)
- Python公式:セキュリティ上の考慮事項(2026年10月2日参照)
関連項目:importしただけで処理が走るのを防ぐ:__main__の使い分け / printからloggingへ:重要度と例外の記録を使い分ける / argparseでヘルプ付きの使いやすいコマンドを作る
