●subprocess.runとは?Pythonでコマンドを実行する魔法
Pythonを使っていると、システムコマンドを実行したい場面にしばしば遭遇しますよね。
そんな時に頼りになるのが、Pythonの標準ライブラリである「subprocess」モジュールです。
その中でも特に便利なのが「subprocess.run」関数。
この関数を使えば、Pythonからシステムコマンドを簡単に実行できちゃうんです。
コマンドの実行結果を取得したり、エラー処理を行ったりと、さまざまな操作が可能になります。
subprocess.runは、Pythonでコマンドを実行する際の強力な味方と言えるでしょう。
今回は、そんなsubprocess.runの魅力に迫っていきたいと思います!
○subprocess.runの基本的な使い方
では早速、subprocess.runの基本的な使い方を見ていきましょう。
subprocess.runを使ってコマンドを実行するには、次のようなコードを書きます。
このコードでは、ls -l
コマンドを実行し、その結果をresult
変数に格納しています。
capture_output=True
を指定することで、コマンドの標準出力と標準エラー出力をキャプチャできます。
また、text=True
を指定することで、キャプチャした出力を文字列として扱うことができます。
実行結果はresult.stdout
で取得できます。この例では、ls -l
コマンドの結果が表示されるはずです。
コマンドの実行が失敗した場合は、subprocess.CalledProcessError
例外が発生します。
この例外をキャッチすることで、エラー処理を行うことができますよ。
この例では、存在しないファイルnonexistent_file
を指定してls
コマンドを実行しています。
check=True
を指定することで、コマンドの実行が失敗した場合に例外が発生するようになります。
例外が発生した場合は、except
ブロックで例外をキャッチし、エラーメッセージを表示しています。
コマンドが正常に実行された場合は、else
ブロックで結果を表示します。
このように、subprocess.runを使えば、Pythonからシステムコマンドを簡単に実行し、結果を取得したり、エラー処理を行ったりできます。
○subprocess.runとsubprocess.callの違い
ここで、少し蛇足かもしれませんが、subprocess.runとsubprocess.callの違いについても触れておきましょう。
実は、subprocess.runは比較的新しい関数で、Python 3.5から導入されました。
それ以前は、subprocess.callを使ってコマンドを実行するのが一般的でした。
subprocess.callとsubprocess.runの主な違いは、subprocess.runがコマンドの実行結果をCompletedProcess
オブジェクトとして返すのに対し、subprocess.callは単にコマンドの終了コードを返すという点です。
また、subprocess.runではキャプチャした出力を文字列として扱うことができますが、subprocess.callでは出力を直接取得することができません。
ですから、基本的にはsubprocess.runを使うことをおすすめします。
subprocess.runの方が機能が豊富で、コードの可読性も高くなるでしょう。
とはいえ、subprocess.callも依然として使われているので、古いコードを読む際には知っておくと良いかもしれませんね。
●10個の実践的なsubprocess.run活用法
さて、subprocess.runの基本的な使い方はわかりましたが、実際の開発現場では、もっと様々な場面でsubprocess.runを活用することができます。
ここからは、subprocess.runを使った実践的な活用法を10個ほど紹介していきたいと思います。
コマンド実行の結果を取得したり、エラー処理を行ったり、タイムアウトを設定したりと、より実用的なテクニックが満載です。
それでは、早速1つ目のサンプルコードから見ていきましょう。
○サンプルコード1:シンプルなコマンド実行
まずは、シンプルなコマンド実行の例から始めましょう。
先ほども紹介したls -l
コマンドを実行するコードです。
このコードを実行すると、カレントディレクトリの詳細なファイル一覧が表示されるはずです。
ls -l
コマンドの出力結果が、result.stdout
に格納されているんですね。
実行結果の例
こんな感じで、subprocess.runを使えば、Pythonから簡単にシステムコマンドを実行し、その結果を取得することができます。
○サンプルコード2:戻り値の取得と活用
次に、コマンドの戻り値を取得し、それを活用する方法を見ていきましょう。
例えば、ping
コマンドを実行し、その戻り値によって処理を分岐させるようなコードを書いてみます。
この例では、ping
コマンドを使って指定したホストに対して疎通確認を行っています。
-c 1
オプションを付けることで、1回だけpingを送信するようにしています。
コマンドの実行結果はresult.returncode
に格納されます。
この値が0であれば、pingが成功したことを意味するので、”reachable”というメッセージを表示します。
0以外の場合は、pingが失敗したことを意味するので、”not reachable”というメッセージを表示するようにしています。
実行結果の例(pingが成功した場合)
実行結果の例(pingが失敗した場合)
このように、subprocess.runの戻り値を活用することで、コマンドの実行結果に応じて処理を切り替えることができます。
○サンプルコード3:標準出力を取得する方法
続いて、コマンドの標準出力を取得する方法について見ていきましょう。
先ほどのls -l
コマンドの例でも少し触れましたが、ここではもう少し詳しく説明します。
標準出力を取得するには、subprocess.run
のcapture_output
パラメータをTrue
に設定し、stdout
属性でアクセスします。
この例では、echo
コマンドを使って”Hello, World!”という文字列を標準出力に出力しています。
capture_output=True
を指定することで、その出力をresult.stdout
に格納しています。
また、text=True
を指定することで、出力を文字列として扱うようにしています。
実行結果
標準出力に出力された”Hello, World!”という文字列が、そのままresult.stdout
に格納され、表示されていますね。
capture_output=True
を指定しない場合、標準出力は直接コンソールに出力されてしまい、result.stdout
では取得できないので注意が必要です。
○サンプルコード4:エラー処理とexception
subprocess.runを使ってコマンドを実行する際、エラーが発生することがあります。
例えば、存在しないコマンドを実行しようとしたり、コマンドの権限がない場合などです。
そんな時は、適切にエラー処理を行うことが大切ですよね。
ここでは、subprocess.runでのエラー処理の方法を見ていきましょう。
エラーが発生した場合、subprocess.runはsubprocess.CalledProcessError
という例外を発生させます。
この例外をキャッチすることで、エラー処理を行うことができます。
この例では、存在しないファイルnonexistent_file
に対してls
コマンドを実行しています。
check=True
を指定することで、コマンドの実行が失敗した場合に例外が発生するようにしています。
例外が発生した場合は、except
ブロックでキャッチし、エラーメッセージや戻り値、出力内容を表示しています。
コマンドが正常に実行された場合は、else
ブロックで結果を表示します。
実行結果
エラーメッセージには、実行したコマンドや戻り値、エラー内容が含まれています。
e.returncode
で戻り値を、e.output
で出力内容を取得することができます。
エラー処理を適切に行うことで、コマンド実行時のトラブルにも柔軟に対応できるようになります。
ただし、check=True
を指定しない場合、例外は発生しません。
その場合は、result.returncode
をチェックすることで、コマンドの実行状態を確認する必要があります。
○サンプルコード5:タイムアウトの設定
コマンドの実行には時間がかかることがあります。
特に、ネットワーク関連のコマンドや、重たい処理を行うコマンドなどは、レスポンスが返ってくるまでに時間がかかってしまうことも。
そんな時は、subprocess.runにタイムアウトを設定することで、一定時間経過後に処理を中断させることができます。
この例では、Googleの公開DNS(8.8.8.8)に対してping
コマンドを実行しています。
timeout=5
を指定することで、5秒経過するとタイムアウトするようにしています。
タイムアウトすると、subprocess.TimeoutExpired
例外が発生します。
この例外をキャッチすることで、タイムアウト時の処理を記述することができます。
実行結果(タイムアウトした場合)
ネットワークの状態によっては、pingが5秒以内に返ってこない場合があります。
そのような場合に、無限に待ち続けるのではなく、一定時間でタイムアウトさせることができるのは便利ですよね。
タイムアウト時間は、コマンドの特性や環境に合わせて適切に設定することが大切です。
○サンプルコード6:環境変数の指定
コマンドを実行する際に、環境変数を指定したい場合があります。
例えば、コマンドの動作を変更したり、APIキーなどの秘密情報を渡したりする場合などです。
subprocess.runでは、env
パラメータを使って環境変数を指定することができます。
この例では、script.py
というPythonスクリプトを実行する際に、API_KEY
とDEBUG
という環境変数を設定しています。
env
パラメータには、環境変数名と値をキーと値に持つ辞書を指定します。
この辞書に指定された環境変数が、コマンド実行時の環境変数としてセットされます。
script.py
の中では、os.environ
を使ってこの環境変数にアクセスすることができます。
実行結果
環境変数を指定することで、コマンドの動作を柔軟に制御することができます。
APIキーなどの秘密情報を環境変数で渡すことで、コードに直接記述するよりも安全に管理することができますね。
ただし、環境変数には機密情報を含めないように注意が必要です。
コマンドの実行ログなどに環境変数の内容が残ってしまう可能性があるからです。
○サンプルコード7:ディレクトリの変更
コマンドを実行する際に、ワーキングディレクトリを変更したい場合があります。
例えば、特定のディレクトリ下でコマンドを実行したい場合などです。
subprocess.runでは、cwd
パラメータを使ってワーキングディレクトリを指定することができます。
この例では、/path/to/directory
ディレクトリに移動した状態で、ls -l
コマンドを実行しています。
cwd
パラメータに、移動先のディレクトリパスを指定します。
実行結果
指定したディレクトリ下でコマンドが実行され、そのディレクトリ内のファイル一覧が表示されます。
ワーキングディレクトリを変更することで、特定のディレクトリ下でコマンドを実行したり、相対パスでファイルを指定したりすることができるようになります。
ただし、指定したディレクトリが存在しない場合は、FileNotFoundError
例外が発生するので注意が必要です。
○サンプルコード8:シェルコマンドの実行
PythonのSubprocess.runを使えば、シェルコマンドも簡単に実行できちゃいます。
でも、ちょっと注意が必要なんです。
シェルコマンドを実行する際は、shell=True
オプションを指定する必要があります。
でも、そうすると、シェルインジェクションの脆弱性が発生する可能性があるんですよね。
こんな感じでコマンドを指定すると、ls -l
だけでなく、rm -rf /tmp/important_dir
も実行されてしまいます。
大切なディレクトリが消えちゃうかもしれません。
怖いですよね。
ですから、シェルコマンドを実行する際は、ユーザーからの入力値を直接コマンドに渡すのは避けるべきです。
代わりに、コマンドとその引数をリストで指定するのがおすすめです。
リストでコマンドと引数を指定することで、シェルインジェクションのリスクを減らすことができます。
でも、どうしてもシェルの機能を使いたい場合もありますよね。
そんな時は、シェルコマンドをリストの要素として渡すことで、安全にシェルコマンドを実行することができます。
こんな感じで、sh -c
を使ってシェルコマンドを渡すことで、パイプやリダイレクトなどのシェルの機能を安全に使うことができるんです。
でも、やっぱりシェルコマンドを実行する際は、十分な注意が必要ですね。
システムに悪影響を与えるコマンドを実行しないように気をつけましょう。
○サンプルコード9:入力のリダイレクト
Subprocess.runでは、コマンドへの入力をリダイレクトすることもできます。
つまり、コマンドにデータを渡すことができるんです。
例えば、cat
コマンドを使って、文字列を標準入力から渡してみましょう。
この例では、input_data
に格納された文字列をcat
コマンドの標準入力にリダイレクトしています。
input
パラメータに文字列やbytes
オブジェクトを指定することで、コマンドにデータを渡すことができます。
実行結果
input_data
の内容がそのままcat
コマンドに渡され、標準出力に出力されているのがわかりますね。
入力のリダイレクトは、コマンドにデータを渡す必要がある場合に便利です。例えば、テキストファイルの内容を処理するコマンドにデータを渡したい場合などに活用できます。
この例では、input.txt
ファイルの内容を読み込み、sed
コマンドに渡しています。
sed
コマンドは、入力データ内の”hello”という文字列を”Hello”に置換しています。
実行結果
ファイルの内容がsed
コマンドによって処理され、置換後の結果が標準出力に出力されました。
入力のリダイレクトを使うことで、ファイルやデータをコマンドに渡し、処理することができます。
データの前処理やフィルタリングなどに活用できそうですね。
○サンプルコード10:非同期実行とバックグラウンド処理
Subprocess.runは、デフォルトではコマンドの実行が完了するまでブロックします。
つまり、コマンドの実行が終わるまで、次の処理に進まないんです。
でも、コマンドの実行に時間がかかる場合や、並行して他の処理を進めたい場合もありますよね。
そんな時は、コマンドを非同期に実行したり、バックグラウンドで処理したりすることができます。
まずは、コマンドを非同期に実行する方法から見ていきましょう。
この例では、sleep
コマンドを使って5秒間の待機を行っています。
subprocess.Popen
を使ってコマンドを実行すると、すぐに次の処理に進みます。
実行結果:
subprocess.Popen
を使うことで、コマンドを非同期に実行できるんです。
print
文が先に実行され、その後にsleep
コマンドが完了するまで待機しています。
process.wait()
を呼び出すことで、コマンドの完了を待つことができます。
非同期に実行されたコマンドの完了を確認したい場合に便利ですね。
次に、コマンドをバックグラウンドで実行する方法を見ていきましょう。
この例では、sleep
コマンドをバックグラウンドで実行しています。
stdout
とstderr
にsubprocess.DEVNULL
を指定することで、コマンドの出力を抑制しています。
subprocess.Popen
を使ってコマンドを実行した後、他の処理を実行することができます。
バックグラウンドで実行されているコマンドは、独立して処理を進めます。
process.poll()
を呼び出すことで、コマンドの実行状態を確認できます。
process.returncode
がNoneの場合は、コマンドがまだ実行中であることを表します。
Noneでない場合は、コマンドが完了したことを示し、その値が終了コードになります。
●subprocess.runのパワーアップテクニック
さて、subprocess.runの基本的な使い方はマスターできましたね。
でも、もっと効率的に、もっと便利にsubprocess.runを使いたいと思いませんか?
ここからは、subprocess.runをさらにパワーアップさせるテクニックをご紹介します。
リアルタイムで標準出力を取得したり、複数のコマンドを連続して実行したりと、より実践的なテクニックが満載ですよ!
それでは、早速1つ目のテクニックから見ていきましょう。
○リアルタイムで標準出力を取得する方法
subprocess.runを使ってコマンドを実行すると、通常はコマンドの実行が完了するまで、標準出力を取得することができません。
でも、コマンドの実行中にリアルタイムで標準出力を取得したい場合もありますよね。
そんな時は、subprocess.Popen
を使って、コマンドの標準出力をリアルタイムで取得することができます。
この例では、ping
コマンドを使ってGoogleへの疎通確認を行っています。
subprocess.Popen
を使ってコマンドを実行し、stdout=subprocess.PIPE
を指定することで、標準出力をパイプで取得できるようにしています。
universal_newlines=True
を指定することで、標準出力を文字列として扱うことができます。
実行結果
process.stdout
をイテレートすることで、コマンドの実行中に標準出力をリアルタイムで取得することができます。
ping
コマンドの出力が1行ずつ表示されているのがわかりますね。
リアルタイムで標準出力を取得できると、コマンドの進行状況を監視したり、途中経過を表示したりすることができます。
ログの出力をリアルタイムで確認したい場合などにも便利ですよ。
○複数のコマンドを連続して実行する
次に、複数のコマンドを連続して実行する方法を見ていきましょう。
タスクの自動化などで、複数のコマンドを順番に実行したい場合がありますよね。
subprocess.runを使えば、複数のコマンドを簡単に連続して実行することができます。
この例では、commands
リストに複数のコマンドを定義しています。
各コマンドは、コマンド名と引数のリストで表現されています。
for
ループを使って、commands
リストの各コマンドを順番に実行しています。
subprocess.run
を使ってコマンドを実行し、標準出力を取得して表示しています。
実行結果
各コマンドが順番に実行され、その出力が表示されました。
sleep
コマンドを使って、コマンドの実行間に一定の間隔を設けています。
複数のコマンドを連続して実行することで、一連の処理を自動化することができます。
例えば、ファイルのバックアップやデータの前処理など、決まった手順で複数のコマンドを実行する場合に便利ですね。
○サブプロセスの終了を確認する方法
最後に、サブプロセスの終了を確認する方法を見ていきましょう。
コマンドを実行した後、そのコマンドが正常に終了したかどうかを確認したい場合がありますよね。
subprocess.runを使えば、コマンドの終了コードを取得することで、サブプロセスの終了状態を確認することができます。
この例では、存在しないファイルnonexistent_file
に対してls
コマンドを実行しています。
result.returncode
を確認することで、コマンドの終了コードを取得することができます。終了コードが0の場合は、コマンドが正常に終了したことを表します。
0以外の場合は、コマンドが異常終了したことを表します。
異常終了した場合は、result.stderr
を使って標準エラー出力を取得し、表示することができます。
実行結果:
ls
コマンドが存在しないファイルに対して実行されたため、異常終了しています。
終了コードが1であることを示し、標準エラー出力にエラーメッセージが表示されました。
サブプロセスの終了状態を確認することで、コマンドの実行結果に応じて適切な処理を行うことができます。
エラーが発生した場合にログを出力したり、リトライ処理を行ったりするのに役立ちますね。
●よくあるエラーと対処法
subprocess.runを使ってコマンドを実行していると、時々エラーに遭遇することがありますよね。
でも、どんなエラーが発生するのか、そしてその対処法を知っておくことで、トラブルに適切に対応できるようになります。
ここでは、subprocess.runを使う際によく遭遇するエラーとその解決策を3つご紹介します。
エラーメッセージを見ただけでは何が原因なのかわからないこともありますが、落ち着いて原因を突き止めていきましょう。
それでは、早速1つ目のエラーから見ていきましょう。
○「FileNotFoundError」の解決策
subprocess.runでコマンドを実行しようとしたら、突然「FileNotFoundError」が発生して困ってしまった経験はありませんか?
「FileNotFoundError」は、指定したコマンドが見つからない場合に発生するエラーです。
つまり、実行しようとしたコマンドが存在しないか、パスが正しく指定されていない可能性があります。
この例では、存在しないコマンドnonexistent_command
を実行しようとしています。
check=True
を指定しているため、コマンドが見つからない場合にFileNotFoundError
が発生します。
実行結果
エラーメッセージを見ると、nonexistent_command
というファイルやディレクトリが存在しないことがわかります。
「FileNotFoundError」が発生した場合は、次の点を確認してみましょう。
- コマンドの名前が正しいか確認する
- コマンドのパスが通っているか確認する
- コマンドが実行可能な状態になっているか確認する
コマンドの名前やパスを正しく指定することで、「FileNotFoundError」を解決することができます。
○「PermissionError」への対応
次に、「PermissionError」について見ていきましょう。
「PermissionError」は、コマンドを実行する権限がない場合に発生するエラーです。
この例では、sudo
コマンドを使って/root
ディレクトリの内容を表示しようとしていますが、適切な権限がない場合に「PermissionError」が発生します。
実行結果
エラーメッセージを見ると、権限が拒否されたことがわかります。
「PermissionError」が発生した場合は、次の点を確認してみましょう。
- 適切な権限でコマンドを実行しているか確認する
- 必要な権限を付与する(sudoなどを使用する)
- セキュリティ上の理由から、権限を付与できない場合は別の方法を検討する
適切な権限でコマンドを実行することで、「PermissionError」を解決することができます。
ただし、セキュリティ上の観点から、不必要な権限の付与は避けるべきです。
○文字化けトラブルの解消法
最後に、文字化けトラブルの解消法について見ていきましょう。
コマンドの実行結果が文字化けしてしまい、正しく表示されないことがありますよね。
文字化けの原因は、主に文字エンコーディングの違いによるものです。
Pythonとコマンドの出力で使用されている文字エンコーディングが一致していないと、文字化けが発生してしまいます。
この例では、ls -l
コマンドを実行し、その出力を表示しています。
しかし、出力が文字化けしてしまうことがあります。
実行結果(文字化けの例)
文字化けを解消するには、次の方法を試してみましょう。
text=True
を指定してコマンドを実行するencoding
パラメータを指定して、適切な文字エンコーディングを設定する- 出力をデコードする
text=True
を指定することで、出力を文字列として扱うことができます。
さらに、encoding
パラメータを使って適切な文字エンコーディングを指定します。
実行結果(文字化け解消後)
文字エンコーディングを適切に設定することで、文字化けを解消することができました。
文字化けトラブルに遭遇した場合は、文字エンコーディングを意識して対処することが大切です。
適切な文字エンコーディングを指定することで、コマンドの出力を正しく扱うことができます。
●subprocess.runの応用例
さて、ここまでsubprocess.runの基本的な使い方やエラー対処法について解説してきましたが、実際の開発現場ではどのようにsubprocess.runを活用できるのでしょうか?
ここからは、subprocess.runを使った実践的な応用例を4つご紹介します。
システム情報の取得からファイル操作の自動化、ネットワーク診断ツールの作成まで、様々なシーンでsubprocess.runが活躍します。
それでは、具体的なサンプルコードを交えながら、subprocess.runの応用例を見ていきましょう。
○サンプルコード11:システム情報の取得
システム管理のタスクでは、OSやハードウェアの情報を取得する必要があることが多いですよね。
subprocess.runを使えば、システム情報を簡単に取得することができます。
例えば、Linuxシステムでメモリ使用量を取得してみましょう。
この例では、free
コマンドを使ってメモリ使用量を取得しています。
-h
オプションを指定することで、人間が読みやすい形式で出力されます。
実行結果
メモリの総容量や使用量、空き容量などの情報が表示されました。
システム監視ツールやリソース管理ツールを作成する際に、subprocess.runを使ってシステム情報を取得することができます。
他にも、uname
コマンドを使ってOSの情報を取得したり、lscpu
コマンドを使ってCPUの情報を取得したりと、様々なシステム情報を取得することができますよ。
○サンプルコード12:ファイル操作の自動化
ファイル操作の自動化も、subprocess.runの得意分野の1つです。
ファイルのコピーや移動、圧縮や解凍など、様々なファイル操作をPythonから実行することができます。
例えば、tar
コマンドを使ってファイルを圧縮してみましょう。
この例では、tar
コマンドを使ってdirectory
ディレクトリをarchive.tar.gz
という名前で圧縮しています。
-czvf
オプションは、圧縮(c
)、gzip形式(z
)、詳細出力(v
)、ファイル名指定(f
)を意味します。
実行結果
圧縮処理の詳細出力が表示され、archive.tar.gz
ファイルが作成されました。
subprocess.runを使えば、バックアップの自動化やファイルの一括処理などを簡単に実装することができます。
他にも、cp
コマンドを使ってファイルをコピーしたり、mv
コマンドを使ってファイルを移動したりと、様々なファイル操作を自動化することができますよ。
○サンプルコード13:ネットワーク診断ツールの作成
ネットワーク関連のタスクでも、subprocess.runが活躍します。
ネットワーク接続の確認やトラブルシューティングに役立つツールを作成することができます。
例えば、ping
コマンドを使って指定したホストに対して疎通確認を行うツールを作ってみましょう。
この例では、ping
コマンドを使ってwww.example.com
に対して5回の疎通確認を行っています。
-c
オプションで送信回数を指定しています。
実行結果
ping
コマンドの実行結果が表示され、指定したホストへの疎通が確認できました。
ネットワーク診断ツールを作成する際に、subprocess.runを使って様々なネットワークコマンドを実行することができます。
他にも、traceroute
コマンドを使って経路情報を取得したり、nslookup
コマンドを使ってDNS情報を取得したりと、ネットワーク関連のタスクを自動化することができますよ。
○サンプルコード14:外部APIとの連携
最後に、外部APIとの連携について見ていきましょう。
subprocess.runを使って、コマンドラインツールを介して外部APIと連携することができます。
例えば、curl
コマンドを使ってWebサービスのAPIを呼び出してみましょう。
この例では、curl
コマンドを使ってhttps://api.example.com/data
というURLに対してGETリクエストを送信しています。
-s
オプションでサイレントモードを指定し、出力をキャプチャしています。
APIから返されたJSONデータは、result.stdout
に格納されています。
json.loads()
を使ってJSONデータをPythonのデータ構造に変換し、表示しています。
実行結果
APIから取得したデータが表示されました。
subprocess.runを使って外部コマンドを実行し、その出力結果を解析することで、外部APIとの連携を実現することができます。
他にも、wget
コマンドを使ってファイルをダウンロードしたり、ftp
コマンドを使ってFTPサーバーとファイル転送したりと、様々な外部サービスとの連携が可能です。
まとめ
さて、ここまでPythonのsubprocess.runについて、基本的な使い方から実践的な活用法まで、幅広く 解説してきました。
今回学んだ知識を生かして、実際の開発プロジェクトでsubprocess.runを活用してみてください。
きっと、Pythonでのシステムコマンド実行が身近なものになるはずです。