AI自動化

Python subprocessで外部コマンドを安全に呼ぶパターン集——自動化パイプラインで詰まった話

Python subprocessで外部コマンドを安全に呼ぶパターン集——自動化パイプラインで詰まった話

この記事は約 4 分で読めます

📖 目次
  1. 📌 導入
  2. 📌 shell=True は原則禁止——なぜ危ないか
  3. 📌 エラーを握りつぶさない——check=True と例外処理
  4. 📌 タイムアウトを必ず設定する
  5. 📌 出力を逐次読みたいとき——Popen を使う
  6. 📌 環境変数を安全に渡す
  7. 📌 作業ディレクトリを明示する
  8. 📌 実用ラッパー関数——パイプライン全体で使い回す
  9. 📌 まとめ

導入

自動化スクリプトを量産していると、必ずぶつかるのが subprocess の罠だ。

(略) で書き始めて、ある日突然コマンドインジェクションで意図しない動作をする。エラーが握りつぶされて無言でsyscallが終わる。タイムアウトを設定し忘れて、ゾンビプロセスがlaunchdのジョブをブロックする。

僕のパイプラインでもやらかした。ブログ投稿スクリプトの中でffmpegを呼ぶ箇所があって、ファイル名にスペースが入ったまま (略) で渡したら、コマンドが途中で切れてサイレントに失敗した。30分くらい原因を探した末にやっと気づいた。

この記事では、Pythonの (略) を本番の自動化パイプラインで安全に使うためのパターンを実際のコードベースから引っ張ってまとめる。


shell=True は原則禁止——なぜ危ないか

まずここから話す。よく見るやつ。

(コード例は元記事を参照)

これ、(略)(略) が入ったら終わる。自動化パイプラインはユーザー入力をそのままファイル名にすることが多いから、外部から仕込まれるリスクが現実的にある。

正しい書き方は引数をリストで渡す。

(コード例は元記事を参照)

シェルを経由しないので、スペースや特殊文字はそのまま引数として扱われる。(略) が必要になるのは、パイプ((略))やリダイレクト((略))をシェルに処理させたいときだけだと思っていい。


エラーを握りつぶさない——check=True と例外処理

(コード例は元記事を参照)

launchdで定期実行するスクリプトは、失敗を無視したまま次のステップに進む。それが一番たちが悪い。

(略) を付けると、コマンドが非ゼロで終了したときに (略) を投げてくれる。

(コード例は元記事を参照)

(略) で stdout/stderr を両方キャプチャ、(略) でバイト列じゃなく文字列として受け取れる。これをセットで覚えておくと後が楽だ。


タイムアウトを必ず設定する

これも見落としやすい。

(コード例は元記事を参照)

(略) を設定しないと、ネットワーク待ちや外部ツールのフリーズでプロセスが永久にブロックする。launchdで動かすジョブは誰も見ていないから、ゾンビのまま次の起動時刻まで居座る。

タイムアウト時は (略) が飛ぶ。

(コード例は元記事を参照)

僕のパイプラインでは、Ollamaへのリクエストやffmpeg変換に長めの (略) を、APIコール系には (略) を設定するようにしている。


出力を逐次読みたいとき——Popen を使う

(略) は完了まで待つ。長時間走るコマンドの出力をリアルタイムで処理したいなら (略) を使う。

(コード例は元記事を参照)

launchdのジョブで動かすとき、長時間のffmpeg変換で進捗を確認したくてこのパターンを使った。(略) をイテレートすると行単位で読める。

ただし (略) はリソース管理を自分でやる必要があるので、(略) ブロックで必ずラップする。


環境変数を安全に渡す

外部コマンドにAPIキーを渡すとき、コマンドライン引数に含めると (略) で丸見えになる。環境変数経由が安全だ。

(コード例は元記事を参照)

(略) で現在の環境変数をコピーして、必要なキーを追加する。これで親プロセスの環境変数も子プロセスに引き継がれる。

逆に特定の環境変数を「除外」したいケースもある。僕のパイプラインでは (略) を呼ぶとき、APIキーが環境変数にあるとサブスクじゃなくAPIクレジットを消費してしまう問題があった。

(コード例は元記事を参照)

これで Claude Code がサブスク経由で動く。実際に気づくまで数日クレジットを無駄にした。


作業ディレクトリを明示する

(コード例は元記事を参照)

(略) を指定しないと、スクリプトを起動したディレクトリで実行される。launchdから呼んだとき、作業ディレクトリが (略) だったりして相対パスが全部崩れることがある。

明示するのが一番手間が少ない。


実用ラッパー関数——パイプライン全体で使い回す

毎回同じ引数を書くのが面倒なので、ラッパーを1つ作って使い回している。

(コード例は元記事を参照)

呼び出し側はシンプルになる。

(コード例は元記事を参照)


まとめ

パターンをまとめると、

  • (略) を使うなら理由を言語化できるときだけ
  • (略) で失敗を必ず検知する
  • (略) を省略しない
  • (略) + (略) はセットで
  • APIキー等は環境変数経由、(略) で見えない場所に
  • (略) は明示する

自動化スクリプトは「誰も見ていない状態で動く」前提で書く必要がある。エラーを握りつぶしたり、タイムアウトを設定し忘れると、失敗したまま次のジョブが動いて整合性が壊れる。launchd + Python で組んでいると特にそれが刺さる。✨

失敗ログを眺めながら少しずつ堅牢にしていくのが、「完璧より継続」のやり方だと思っている。


(コード例は元記事を参照)

👨‍💻
一ノ瀬泰斗
AI自動化エンジニア / Python個人開発者

Claude Code × Ollama × launchd で SNS・ブログ・KDPを全自動化。実測データと失敗談を軸に、月5万円収益化のリアルな記録を発信中。