Python subprocess チート:標準入出力・パイプ・非同期実行の最短レシピ
Python でシェルコマンドや外部プログラムを実行したいとき、subprocess モジュールは強力な味方になります。この記事では、標準入出力の制御からパイプ処理、非同期実行まで、実務で即使える最短レシピを一挙にご紹介します。
初めて subprocess を使う方でも、この記事を読めば基本から応用まで網羅的に理解できるでしょう。
subprocess 早見表
まず、よく使う関数とオプションを早見表でまとめました。必要な処理を素早く見つけられます。
主要関数比較表
| # | 関数名 | 用途 | 戻り値 | 待機 | 推奨度 |
|---|---|---|---|---|---|
| 1 | subprocess.run() | 同期実行(Python 3.5+) | CompletedProcess | する | ★★★ |
| 2 | subprocess.Popen() | 低レベル制御・非同期 | Popen オブジェクト | しない | ★★☆ |
| 3 | subprocess.call() | 同期実行(旧式) | 終了コード | する | ★☆☆ |
| 4 | subprocess.check_output() | 出力取得(旧式) | bytes | する | ★☆☆ |
標準入出力制御オプション表
| # | オプション名 | 値 | 説明 | 使用例 |
|---|---|---|---|---|
| 1 | stdin | subprocess.PIPE | 標準入力をパイプ経由で渡す | コマンドに文字列を送る |
| 2 | stdout | subprocess.PIPE | 標準出力をキャプチャ | 実行結果を取得する |
| 3 | stderr | subprocess.PIPE | 標準エラーをキャプチャ | エラーメッセージを取得 |
| 4 | stderr | subprocess.STDOUT | エラーを標準出力に統合 | 出力を一つにまとめる |
| 5 | stdout | subprocess.DEVNULL | 出力を破棄 | 不要な出力を抑制 |
よく使うオプション表
| # | オプション名 | 型 | 説明 | デフォルト |
|---|---|---|---|---|
| 1 | shell | bool | シェル経由で実行 | False |
| 2 | check | bool | エラー時に例外を発生 | False |
| 3 | text | bool | 文字列として扱う(Python 3.7+) | False |
| 4 | encoding | str | 文字エンコーディング指定 | None |
| 5 | timeout | float | タイムアウト秒数 | None |
| 6 | cwd | str | 作業ディレクトリ | None |
| 7 | env | dict | 環境変数 | None |
背景
Python で外部コマンドを実行する必要性
Python は強力なプログラミング言語ですが、システム管理やデータ処理では外部コマンドと連携する場面が多くあります。
たとえば、git コマンドでバージョン管理を行ったり、ffmpeg で動画変換を実行したり、シェルスクリプトを Python から呼び出したりするケースです。こうした外部プログラムとの橋渡しを担うのが subprocess モジュールなのです。
subprocess モジュールの位置づけ
subprocess は Python 2.4 で導入され、古い os.system() や os.popen() を置き換える形で発展してきました。
以下の図は、Python から外部プログラムを実行する際の基本フローを示しています。
mermaidflowchart LR
python["Python プログラム"] -->|コマンド実行| subprocess["subprocess<br/>モジュール"]
subprocess -->|プロセス起動| shell["シェル/外部<br/>プログラム"]
shell -->|標準出力/エラー| subprocess
subprocess -->|結果返却| python
このように、Python は subprocess を通じて外部プログラムを起動し、その実行結果や出力を受け取ります。
図で理解できる要点:
- Python と外部プログラムの間に
subprocessが介在する - 双方向でデータをやり取りできる
- 標準入出力を通じて柔軟に連携可能
Python 3.5 以降の変化
Python 3.5 で subprocess.run() が追加され、より直感的で安全な API が提供されるようになりました。それ以前は call() や check_output() を使い分ける必要がありましたが、現在は run() 一本で多くのケースに対応できます。
Python 3.7 では text パラメータが導入され、文字列処理がさらに簡単になりました。このように、subprocess は時代とともに進化を続けているのです。
課題
初心者が直面する 3 つの壁
subprocess を使い始めると、多くの開発者が以下の課題に直面します。
1. 標準入出力の制御が分かりにくい
外部コマンドの実行結果をどう取得するのか、エラー出力をどう扱うのか、初見では戸惑うことが多いです。
stdout=subprocess.PIPE や stderr=subprocess.STDOUT といったオプションの意味を理解するまでに時間がかかります。
2. パイプ処理の複雑さ
Unix/Linux では grep や awk を組み合わせてパイプ処理を行いますが、Python でこれを再現しようとすると、複数の Popen オブジェクトを連結する必要があり、コードが複雑になりがちです。
3. 同期実行と非同期実行の使い分け
短時間で終わるコマンドなら同期実行で問題ありませんが、時間のかかる処理や複数コマンドの並列実行では非同期処理が必要になります。
しかし、Popen を使った非同期実行は初心者には難易度が高いのです。
以下の図は、同期実行と非同期実行の違いを示しています。
mermaidstateDiagram-v2
state "同期実行" as sync_mode {
[*] --> CommandStart: run() 呼び出し
CommandStart --> Waiting: プロセス起動
Waiting --> ResultReturned: 完了待ち
ResultReturned --> [*]: 結果取得
}
state "非同期実行" as async_mode {
[*] --> ProcStart: Popen() 呼び出し
ProcStart --> Continue: すぐ制御戻る
Continue --> OtherWork: 他の処理実行
OtherWork --> CheckResult: poll()/wait()
CheckResult --> [*]: 結果取得
}
図で理解できる要点:
- 同期実行は完了まで待機してから結果を返す
- 非同期実行はすぐに制御が戻り、他の処理を並行できる
- 用途に応じて使い分けることが重要
セキュリティリスク
shell=True を安易に使うと、シェルインジェクション攻撃のリスクが生じます。
ユーザー入力をそのままコマンドに渡すと、悪意のあるコマンドが実行される可能性があるため、注意が必要です。
解決策
基本方針:run() を第一選択に
Python 3.5 以降では、ほとんどのケースで subprocess.run() を使うことをおすすめします。
シンプルで安全、かつ必要な機能が揃っているからです。非同期処理が必要な場合のみ Popen() を検討しましょう。
標準入出力制御の基本パターン
標準入出力を制御するには、stdin、stdout、stderr パラメータを使います。以下が代表的なパターンです。
パターン 1: 標準出力を取得する
これは最も基本的な使い方です。コマンドの実行結果を Python で受け取ります。
pythonimport subprocess
python# ls コマンドの結果を取得
result = subprocess.run(
['ls', '-la'],
stdout=subprocess.PIPE, # 標準出力をキャプチャ
text=True # 文字列として扱う
)
python# 結果を表示
print(result.stdout)
print(f"終了コード: {result.returncode}")
text=True を指定すると、出力が文字列(str 型)で返ってきます。指定しない場合はバイト列(bytes 型)になるため、注意が必要です。
パターン 2: 標準エラーも同時に取得する
エラー出力を別々に取得したい場合は、stderr=subprocess.PIPE を追加します。
pythonresult = subprocess.run(
['python', 'script.py'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, # エラー出力も取得
text=True
)
python# 出力とエラーを分けて表示
print("標準出力:", result.stdout)
print("標準エラー:", result.stderr)
これにより、正常な出力とエラーメッセージを明確に区別できます。
パターン 3: エラーを標準出力に統合する
エラーと標準出力を一つにまとめたい場合は、stderr=subprocess.STDOUT を使います。
pythonresult = subprocess.run(
['python', 'script.py'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # エラーを標準出力に統合
text=True
)
python# すべての出力が stdout に含まれる
print("全出力:", result.stdout)
ログファイルに全出力をまとめて記録する際に便利です。
パイプ処理の実現方法
Unix/Linux の command1 | command2 のようなパイプ処理を Python で実現するには、2 つの方法があります。
方法 1: shell=True を使う(簡単だが注意が必要)
シェルのパイプ機能をそのまま使う方法です。
python# 'ls -la | grep .py' と同じ
result = subprocess.run(
'ls -la | grep .py',
shell=True, # シェルを経由
stdout=subprocess.PIPE,
text=True
)
pythonprint(result.stdout)
この方法は簡単ですが、shell=True はセキュリティリスクがあるため、ユーザー入力を含む場合は避けるべきです。
方法 2: Popen でパイプを連結する(推奨)
より安全な方法は、Popen を使って複数のプロセスを明示的に連結することです。
python# 最初のコマンド(ls -la)
proc1 = subprocess.Popen(
['ls', '-la'],
stdout=subprocess.PIPE
)
python# 2番目のコマンド(grep .py)
proc2 = subprocess.Popen(
['grep', '.py'],
stdin=proc1.stdout, # proc1の出力を入力に
stdout=subprocess.PIPE,
text=True
)
python# proc1 の stdout を閉じる(重要)
proc1.stdout.close()
python# 最終結果を取得
output, _ = proc2.communicate()
print(output)
proc1.stdout.close() は SIGPIPE を正しく処理するために必要です。これを忘れると、プロセスが正常に終了しないことがあります。
以下の図は、パイプ処理のデータフローを示しています。
mermaidflowchart LR
proc1["プロセス1<br/>(ls -la)"] -->|stdout| pipe1["PIPE"]
pipe1 -->|stdin| proc2["プロセス2<br/>(grep .py)"]
proc2 -->|stdout| result["最終結果"]
python["Python"] -->|起動| proc1
python -->|起動| proc2
result -->|取得| python
図で理解できる要点:
- プロセス 1 の標準出力がプロセス 2 の標準入力になる
- Python が両プロセスを管理し、最終結果を取得する
- パイプを介して安全にデータを受け渡しできる
非同期実行のパターン
時間のかかるコマンドを実行する際、Python プログラムをブロックせずに並行して作業したい場合は、Popen を使います。
基本的な非同期実行
python# コマンドを起動(すぐに制御が戻る)
proc = subprocess.Popen(
['sleep', '10'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
python# この間に他の処理ができる
print("コマンド実行中...")
python# 完了を待つ
stdout, stderr = proc.communicate()
print(f"終了コード: {proc.returncode}")
communicate() は、プロセスの完了を待ち、標準出力とエラー出力を返します。
タイムアウト付き実行
長時間実行されるコマンドには、タイムアウトを設定できます。
pythontry:
result = subprocess.run(
['python', 'long_script.py'],
timeout=5, # 5秒でタイムアウト
stdout=subprocess.PIPE,
text=True
)
except subprocess.TimeoutExpired:
print("Error: タイムアウトしました")
これにより、無限ループなど予期しない状況でプログラムが固まるのを防げます。
進行状況の確認
実行中のプロセスの状態を確認するには、poll() メソッドを使います。
pythonproc = subprocess.Popen(['sleep', '5'])
pythonimport time
while proc.poll() is None:
print("まだ実行中...")
time.sleep(1)
pythonprint(f"完了しました。終了コード: {proc.returncode}")
poll() は、プロセスが終了していれば終了コードを、実行中なら None を返します。
具体例
ここでは、実務でよく使われる具体的なユースケースを紹介します。
例 1: Git コマンドの実行と出力取得
バージョン管理システムとの連携は、開発現場でよくある要件です。
pythonimport subprocess
python# 現在のブランチ名を取得
result = subprocess.run(
['git', 'branch', '--show-current'],
stdout=subprocess.PIPE,
text=True,
check=True # エラー時に例外発生
)
pythonbranch_name = result.stdout.strip()
print(f"現在のブランチ: {branch_name}")
check=True を指定すると、コマンドが失敗した場合(終了コードが 0 以外)に subprocess.CalledProcessError 例外が発生します。
エラーハンドリング付きの例
pythontry:
result = subprocess.run(
['git', 'status', '--short'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True
)
print("変更ファイル:")
print(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Error: Git コマンドが失敗しました")
print(f"終了コード: {e.returncode}")
print(f"エラー内容: {e.stderr}")
このように、エラー情報を詳細に取得して適切に処理できます。
例 2: ログファイルの grep 処理
大きなログファイルから特定のパターンを抽出する場合、grep コマンドを活用すると効率的です。
python# エラーログだけを抽出
result = subprocess.run(
['grep', 'ERROR', '/var/log/app.log'],
stdout=subprocess.PIPE,
text=True
)
python# 結果を1行ずつ処理
for line in result.stdout.splitlines():
# タイムスタンプを抽出するなど
print(f"エラー発見: {line[:50]}...")
grep が何も見つからない場合、終了コードは 1 になりますが、これはエラーではないため、check=True は使わない方が良いでしょう。
例 3: 複数コマンドの並列実行
複数の独立したコマンドを同時に実行することで、処理時間を大幅に短縮できます。
python# 複数のスクリプトを同時実行
processes = []
# スクリプトを起動
for script in ['script1.py', 'script2.py', 'script3.py']:
proc = subprocess.Popen(
['python', script],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
processes.append((script, proc))
python# すべての完了を待つ
results = []
for script, proc in processes:
stdout, stderr = proc.communicate()
results.append({
'script': script,
'returncode': proc.returncode,
'stdout': stdout,
'stderr': stderr
})
python# 結果を確認
for res in results:
print(f"{res['script']}: 終了コード {res['returncode']}")
if res['returncode'] != 0:
print(f" エラー: {res['stderr']}")
このパターンを使えば、シリアル実行では 30 秒かかる処理が、並列実行で 10 秒に短縮されることもあります。
例 4: 標準入力にデータを渡す
コマンドに対して標準入力経由でデータを送る方法です。
python# Python スクリプトに JSON データを渡す
import json
data = {'name': 'Alice', 'age': 30}
json_str = json.dumps(data)
pythonresult = subprocess.run(
['python', 'process_json.py'],
input=json_str, # 標準入力として渡す
stdout=subprocess.PIPE,
text=True
)
pythonprint(result.stdout)
input パラメータを使うことで、簡単にデータを送り込めます。
例 5: 環境変数を設定して実行
特定の環境変数を設定してコマンドを実行したい場合は、env パラメータを使います。
pythonimport os
# 現在の環境変数をコピー
env = os.environ.copy()
python# 追加の環境変数を設定
env['DEBUG'] = '1'
env['API_KEY'] = 'secret-key-12345'
python# 環境変数付きで実行
result = subprocess.run(
['python', 'api_client.py'],
env=env,
stdout=subprocess.PIPE,
text=True
)
os.environ.copy() で現在の環境変数をコピーしてから、必要な変数を追加するのがポイントです。
例 6: 作業ディレクトリを変更して実行
特定のディレクトリでコマンドを実行したい場合は、cwd パラメータを使います。
pythonresult = subprocess.run(
['ls', '-la'],
cwd='/tmp/work', # このディレクトリで実行
stdout=subprocess.PIPE,
text=True
)
pythonprint(result.stdout)
Python スクリプト自体のカレントディレクトリは変わらないため、安全に別ディレクトリでコマンドを実行できます。
以下の図は、よくある subprocess の使用パターンを示しています。
mermaidflowchart TD
start["コマンド実行<br/>開始"] --> sync_q{"同期/非同期<br/>どちら?"}
sync_q -->|同期| run_func["run() 使用"]
sync_q -->|非同期| popen_func["Popen() 使用"]
run_func --> output_q{"出力取得<br/>必要?"}
output_q -->|はい| pipe_out["stdout=PIPE"]
output_q -->|いいえ| no_capture["stdout 指定なし"]
pipe_out --> error_q{"エラー処理<br/>必要?"}
error_q -->|はい| check_true["check=True"]
error_q -->|いいえ| check_false["check=False"]
popen_func --> async_wait["communicate() で<br/>完了待ち"]
async_wait --> poll_check["poll() で<br/>状態確認"]
check_true --> done["実行完了"]
check_false --> done
no_capture --> done
poll_check --> done
図で理解できる要点:
- 同期/非同期の選択が最初の分岐点
- 出力取得の有無でオプションが変わる
- エラー処理の要否で
checkパラメータを使い分ける
まとめ
この記事では、Python の subprocess モジュールを使った外部コマンド実行の実践テクニックを解説しました。
重要なポイント:
-
基本は
run()を使う: Python 3.5 以降では、ほとんどのケースでsubprocess.run()が最適です。シンプルで安全、かつ必要十分な機能を備えています。 -
標準入出力の制御:
stdout=subprocess.PIPEで出力を取得し、text=Trueで文字列として扱うのが基本パターンです。エラー出力はstderr=subprocess.PIPEまたはstderr=subprocess.STDOUTで制御できます。 -
パイプ処理: 複数コマンドの連携は、
Popenで明示的にパイプを連結する方法が安全です。shell=Trueは簡単ですが、セキュリティリスクに注意が必要です。 -
非同期実行: 時間のかかる処理や並列実行には
Popenを使い、communicate()やpoll()で制御します。タイムアウトも設定できます。 -
エラー処理:
check=Trueで自動的に例外を発生させるか、終了コードを確認して手動で処理するかを使い分けましょう。 -
セキュリティ: ユーザー入力を含むコマンド実行では、リスト形式でコマンドを渡し、
shell=Trueは避けるのが鉄則です。
subprocess は外部プログラムとの強力な橋渡し役です。この記事で紹介したパターンを使えば、シェルスクリプトに頼らず、Python だけで柔軟なシステム連携が実現できるでしょう。
最初は複雑に感じるかもしれませんが、基本パターンを押さえれば、応用は無限大です。ぜひ実際のプロジェクトで試してみてください。
関連リンク
articleRuby と Python を徹底比較:スクリプト・Web・データ処理での得意分野
articlePython subprocess チート:標準入出力・パイプ・非同期実行の最短レシピ
articlePython Dev Containers 完全レシピ:再現可能な開発箱を VS Code で作る
articlePython ORMs 実力検証:SQLAlchemy vs Tortoise vs Beanie の選び方
articlePython UnicodeDecodeError 撲滅作戦:エンコーディング自動判定と安全読込テク
articlePython エコシステム地図 2025:データ・Web・ML・自動化の最短ルート
articleTauri vs Electron vs Flutter デスクトップ:UX・DX・配布のしやすさ徹底比較
articleRuby と Python を徹底比較:スクリプト・Web・データ処理での得意分野
articleshadcn/ui のバンドルサイズ影響を検証:Tree Shaking・Code Split の実測データ
articleRedis Docker Compose 構築:永続化・監視・TLS まで 1 ファイルで
articleRemix を選ぶ基準:認証・API・CMS 観点での要件適合チェック
articleReact で管理画面を最短構築:テーブル・フィルタ・権限制御の実例
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来