subprocess.run() で外部コマンドを実行するだけなら簡単だが、実際にはコマンドの出力結果を Python 側で受け取って処理したい場面のほうが多い。ディスク使用量を取得して集計する、Git のログを解析する、外部ツールの出力をパースしてレポートを生成するなど、出力のキャプチャは subprocess を使う上で欠かせない機能だ。
capture_output=True で出力を取得する
Python 3.7 以降では、capture_output=True を指定するだけで標準出力と標準エラー出力の両方をキャプチャできる。
import subprocess result = subprocess.run(["echo", "Hello"], capture_output=True, text=True) print(result.stdout) # Hello\n print(result.stderr) # (空文字列)
text=True を併用しているのがポイントだ。これを指定しないと stdout と stderr はバイト列(bytes)として返される。text=True を付ければ文字列(str)として扱えるため、後続の処理が格段に楽になる。
stdout/stderr が str 型で返る。文字列処理にそのまま使える
stdout/stderr が bytes 型で返る。デコードが必要になる
PIPE を使った方法
capture_output=True は内部的に stdout=subprocess.PIPE, stderr=subprocess.PIPE を設定しているだけだ。Python 3.6 以前や、stdout だけをキャプチャしたい場合は PIPE を直接指定する。
import subprocess # stdout のみキャプチャ(stderr はそのまま端末に出る) result = subprocess.run( ["ls", "-la"], stdout=subprocess.PIPE, text=True ) print(result.stdout)
import subprocess # stdout と stderr を両方キャプチャ result = subprocess.run( ["ls", "/nonexistent"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) print("stdout:", result.stdout) print("stderr:", result.stderr)
capture_output=True と stdout=PIPE を同時に指定するとエラーになるため、どちらか一方だけを使う。基本的には capture_output=True で十分だが、片方だけキャプチャしたいときは PIPE を直接指定するとよい。
出力を加工する実践例
キャプチャした出力を Python で加工する典型的なパターンをいくつか見てみよう。
import subprocess # ディスク使用量を取得して表示 result = subprocess.run( ["df", "-h", "/"], capture_output=True, text=True, check=True ) lines = result.stdout.strip().split("\n") for line in lines: print(line)
コマンドの出力は末尾に改行を含むことが多いため、strip() で余分な空白を除去してから split("\n") で行ごとに分割するのが定番のパターンだ。
もう少し実用的な例として、Git のコミットログから情報を抽出してみる。
import subprocess result = subprocess.run( ["git", "log", "--oneline", "-5"], capture_output=True, text=True, check=True ) commits = result.stdout.strip().split("\n") for i, commit in enumerate(commits, 1): hash_val, message = commit.split(" ", 1) print(f"{i}. [{hash_val}] {message}")
外部コマンドの出力をそのまま表示するだけでなく、Python のデータ構造に変換して再利用できるのが subprocess の強みだ。
stderr を stdout にまとめる
コマンドによっては、通常の出力と警告メッセージが stdout と stderr に分かれて出力される。これらをまとめて 1 つのストリームとして扱いたい場合は stderr=subprocess.STDOUT を使う。
import subprocess result = subprocess.run( ["python3", "-c", "import sys; print('out'); print('err', file=sys.stderr)"], capture_output=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True ) print(result.stdout) # out # err
stderr=subprocess.STDOUT は stderr の内容を stdout に合流させるため、result.stdout に両方の出力が時系列順で入る。result.stderr は None になる点に注意が必要だ。
なお、capture_output=True と stderr=subprocess.STDOUT は併用できない。capture_output=True は内部で stderr=PIPE を設定するため、競合が発生する。
ValueError: stderr and capture_output may not both be set というエラーになる。
encoding パラメータ
text=True の代わりに encoding パラメータで文字コードを明示的に指定することもできる。日本語環境では Shift_JIS で出力するコマンドに遭遇することがあり、そうした場合に役立つ。
import subprocess # Windows の dir コマンドなど、Shift_JIS で出力するケース result = subprocess.run( ["some_command"], capture_output=True, encoding="shift_jis" )
encoding を指定すると text=True は不要になる。逆に text=True を指定した場合はシステムのデフォルトエンコーディングが使われる。文字化けが起きたときは、まず encoding の明示指定を試してみるのがよい。
エラー時の出力を取得する
check=True と capture_output=True を組み合わせると、コマンドが失敗した場合でも CalledProcessError の属性から出力を取得できる。
import subprocess try: result = subprocess.run( ["python3", "-c", "raise ValueError('test error')"], capture_output=True, text=True, check=True ) except subprocess.CalledProcessError as e: print(f"終了コード: {e.returncode}") print(f"stderr: {e.stderr}")
例外オブジェクトの e.stdout と e.stderr にキャプチャされた出力が格納されているため、エラーの原因を解析するのに使える。ログに記録したり、ユーザーにわかりやすいエラーメッセージを組み立てたりする際に重宝する。
出力のキャプチャは subprocess を実用レベルで使いこなすための必須知識だ。capture_output=True と text=True の組み合わせを基本形として覚えておけば、大半のユースケースに対応できるだろう。