Python のプログラムから ls や git のようなコマンドを動かすときは、標準ライブラリの subprocess を使います。subprocess.run にコマンドと引数をリストで渡すと、コマンドが終わるまで待ち、終了コードや出力を返します。
実行結果は Linux の Python 3.12 で動かしたものです。Windows で違う点は最後にまとめました。
基本の書き方
import subprocess
result = subprocess.run(["echo", "こんにちは"], capture_output=True, text=True)
print(result.returncode)
print(result.stdout)
0
こんにちは
コマンドは、空白で区切った単語ごとにリストの要素にします。ls -l /tmp なら ["ls", "-l", "/tmp"] です。
capture_output=True を付けると、コマンドが画面に出す内容を result.stdout(標準出力)と result.stderr(標準エラー)で受け取れます。付けないと、出力は画面にそのまま出て、変数には入りません。text=True は、出力を bytes ではなく文字列で受け取る指定です。result.returncode は終了コードで、0 なら成功です。
失敗したら止める
subprocess.run は、コマンドが失敗しても例外を出しません。returncode を見ないと、失敗に気づかないまま次の処理に進みます。check=True を付けると、終了コードが 0 以外のときに subprocess.CalledProcessError が出ます。
try:
subprocess.run(["ls", "/no-such-dir"], capture_output=True, text=True, check=True)
except subprocess.CalledProcessError as e:
print("失敗:", e.returncode)
print(e.stderr)
失敗: 2
ls: cannot access '/no-such-dir': No such file or directory
コマンドそのものが見つからないときは、check=True を付けていなくても FileNotFoundError になります。
try:
subprocess.run(["no-such-command"])
except FileNotFoundError as e:
print("FileNotFoundError:", e)
FileNotFoundError: [Errno 2] No such file or directory: 'no-such-command'
パイプを使う shell=True と、その危険
|(パイプ)や * などシェルの書き方を使いたいときは、shell=True を付けて、コマンドを1つの文字列で渡します。
r = subprocess.run("ls / | wc -l", shell=True, capture_output=True, text=True)
print(r.stdout)
20
shell=True で外から来た文字をつなげるのは危険です。ファイル名に ; を入れると、別のコマンドまで動きます。
filename = "memo.txt; echo 別のコマンドも動いた"
# shell=True で文字列をつなげた場合
r = subprocess.run(f"cat {filename}", shell=True, capture_output=True, text=True)
print(r.stdout, r.stderr)
# リストで渡した場合
r = subprocess.run(["cat", filename], capture_output=True, text=True)
print(r.stdout, r.stderr)
別のコマンドも動いた
cat: memo.txt: No such file or directory
cat: 'memo.txt; echo 別のコマンドも動いた': No such file or directory
上は echo まで実行されました。ここに rm が入っていたら、ファイルが消えます。下のようにリストで渡すと、全体が1つのファイル名として扱われ、echo は動きません。利用者の入力やファイル名を使うときは、shell=True を付けずにリストで渡します。パイプが必要なら、出力を Python で受け取ってから処理するほうが安全です。
時間がかかりすぎたら止める
timeout に秒数を渡すと、時間内に終わらないコマンドを止めて subprocess.TimeoutExpired を出します。止まったままになりうるコマンド(ネットワークにつなぐものなど)には付けておきます。
try:
subprocess.run(["sleep", "5"], timeout=1)
except subprocess.TimeoutExpired:
print("時間切れ")
時間切れ
os.system との違い
古いコードでは os.system("ls -l") もよく見かけます。os.system は終了コードしか返さず、出力を変数で受け取れません。いつも shell=True と同じ動きをするので、上の危険もそのままです。
code = os.system("echo os.system から実行")
print("戻り値:", code)
os.system から実行
戻り値: 0
新しく書くなら subprocess.run を使います。
実際に動かすと、os.system のコマンドの出力が、プログラムのほかの print より先に画面に出ることがありました。Python の print の出力はいったんためられ、コマンドの出力は直接出るためです。順番が大事なときは、print(..., flush=True) にしておきます。
Windows で動かすとき
Windows では、dir や echo はコマンドプロンプトの中にある命令で、単独のプログラムではありません。subprocess.run(["dir"]) は FileNotFoundError になるので、shell=True を付けて subprocess.run("dir", shell=True) と書きます。git や python のようにプログラムとしてあるコマンドは、Linux と同じくリストで渡せます。
日本語の出力が文字化けするときは、text=True の代わりに encoding="utf-8" や encoding="cp932" を指定して、コマンドが出す文字コードに合わせます。
なお、ターミナルで python と打つと出る >>> の画面(Python シェル)は、ここで扱った OS のシェルとは別のものです。そちらの使い方は「Pythonシェル(対話モード)の使い方:起動・終了とOSのシェルとの違い」にまとめています。
参考リンク
- Python ドキュメント: subprocess — サブプロセス管理
- Python ドキュメント: subprocess のセキュリティで考慮すべき点
- Python ドキュメント: os.system
アイキャッチ画像: Photo by Fernando Hernandez on Unsplash

