Pythonのsubprocess.runを使った外部コマンドの実行

プログラミング

多くのプログラミング言語には、外部コマンドを実行するための機能が備わっています。

プログラムからOSのコマンドを実行することによって、プログラムでは複雑になってしまう処理を簡単に行うことが出来ることもあります。

本記事では、Pythonのコマンド実行について解説します。

記事内に記載しているプログラムは、Python3.9.1を使って動作確認をしています。

Pythonのsubprocess.runを使った外部コマンドの実行

Pythonの外部コマンドの実行について、以下の内容を解説します。

  • Pythonの外部コマンドの実行方法
  • subprocess.runの基本形
  • シェル形式での実行
  • エラー情報の取得
  • プログラムから値を渡す
  • 非同期での実行
  • コマンド実行の補助機能

Pythonの外部コマンドの実行方法

Pythonにおいて、外部コマンドを実行する方法はいくつかあります。

Python2系では、os.systemモジュールやcommandsモジュールによるコマンド実行が主流でしたが、Python3系ではsubprocessモジュールを利用することが推奨されています。

subprocessモジュールには、用途によって使い分けるために、以下の機能が提供されています。

  • subprocess.call:引数に指定したコマンドを実行する。
  • subprocess.check_call:引数に指定したコマンドを実行し、失敗した場合はCalledProcessError例外をスローする。
  • subprocess.check_output:引数に指定したコマンドを実行し、出力結果を返す。

そして、Python3.5からは、上記の3つをまとめた subprocess.run が使えるようになりました。

Python3.9が最新の現状においては、subprocess.run を使うことが最良の選択と言えます。

subprocess.runの基本形

subprocess.runを使うには、subprocessモジュールをimportする必要があります。

import subprocess

runの引数には、指定方法がたくさんあります。基本形が以下のサンプルコードになります。

result = subprocess.run(
    ["date"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(result.returncode) # -> 0
print(result.stdout) # -> 2021年 1月 12日 火曜日 12:52:48 JST

第1引数に実行するコマンドを指定します。
コマンドのオプションが必要な場合は、配列に指定していきます。

stdoutとstderrにPIPEを指定することで、標準出力と標準エラー出力をコマンドの戻り値として取得します。
指定しないと、実行結果がPythonの標準出力およびエラー出力にそのまま出力されます。

textは、実行結果を文字列形式で取得できるようにします。
Python3.7から追加された機能で、それまではバイナリコードで取得されていました。

コマンドの結果は、CompletedProcess型で返されます。
returncodeが、コマンドの終了コードです。
stdoutが、標準出力の結果です。

シェル形式での実行

実行するコマンドにたくさんのオプションを付けたい場合や、複数のコマンドを一括で実行したい場合などには、それらを組み立てた文字列をそのまま渡す方が楽です。
その場合には、shellオプションにTrueを指定します。

result = subprocess.run(
    "date '+%Y%m%d-%H%M%S'", shell=True,
    stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(result.returncode) # -> 0
print(result.stdout) # -> 20210112-125248

利用する場合は、シェルインジェクションとならないように、入力値をチェックしたり、適切にエスケープするなどの対応が必要です。

エラー情報の取得

コマンドの実行に失敗した場合は、returncodeが0以外の数値となり、標準エラー出力から内容を取得することができます。

result = subprocess.run(
    "notcommand", shell=True,
    stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(result.returncode) # -> 127
print(result.stdout) # -> (なし)
print(result.stderr) # -> /bin/sh: notcommand: コマンドが見つかりません

また、引数のcheckオプションにTrueを指定すると、エラーが発生した際にCalledProcessError例外がスローされます。

プログラムから値を渡す

コマンドの実行時に、プログラム内の値を渡したい場合は、引数のinputオプションに指定します。

str = "Hello"
result = subprocess.run(
    "cat", shell=True, input=str, 
    stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(result.returncode) # -> 0
print(result.stdout) # -> Hello

inputで指定した値は、実行したコマンドの標準入力としてパイプで渡されます。
コマンドはパイプで受け取れるコマンドである必要があります。

非同期での実行

subprocess.runは、同期実行であるため、実行するコマンドが終了するまで待機します。
実行時間の長いコマンドである場合や、複数の処理を効率よく実行するには非同期による実行が効果的である場合があります。

非同期で実行する場合は、subprocess.Popenを使います。

proc = subprocess.Popen(
    "sleep 5; date; notcommand", shell=True,
    stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
result = proc.communicate()
print(result)
# -> ('2021年 1月 12日 火曜日 12:52:53 JST\n', '/bin/sh: notcommand: コマンドが見つかりません\n')

上記のプログラムでは、3つのコマンドを一括で非同期に実行しています。

proc.communicateによって、コマンドが終了するまで待機します。
結果は、標準出力と標準エラー出力がタプルとして返されます。

コマンド実行の補助機能

コマンド実行時に取得できる情報や、可能な操作がいくつかありますので紹介します。

プロセスIDの取得

実行しているコマンドのプロセスIDを取得するには、Popenで実行した戻り値からpidで取得することができます。

proc = subprocess.Popen(
    "sleep 5", shell=True,
    stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(proc.pid) # -> 5327

コマンドが実行中であるかを取得

コマンドが実行中であるかどうかのステータスを取得するには、pollを使います。

proc = subprocess.Popen(
    "sleep 5", shell=True,
    stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(proc.poll()) # -> None
proc.terminate()
proc.wait()
print(proc.poll()) # -> -15

実行中である場合には、Noneが返されます。
終了している場合には、ステータスコードが返されます。

コマンドを強制終了する

コマンドの実行中に強制終了する場合は、killを使います。

proc = subprocess.Popen(
    "sleep 5", shell=True,
    stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(proc.poll()) # -> None
proc.kill()
proc.wait()
print(proc.poll()) # -> -9

まとめ

Pythonの外部コマンド実行についてまとめると、以下となります。

  • 外部コマンドを実行する方法はいくつかあるが、Python3.5以上であれば subprocess.run を使うのが最良の選択である。
  • 実行方法として同期形式と非同期形式があり、使用場面に応じて使い分ける。
  • 実行結果を受け取ったり、プログラムから値を入力したり様々な使い方ができる。

マルチOSで使用するアプリケーションの場合は、コマンドの違いを考慮する必要があります。
また、コマンドインジェクション等の脆弱性にも注意が必要です。

ですが、OSコマンドを利用することで、プログラムで実現できる範囲が広がるので、必要な場合には使ってみてはいかがでしょうか。

 

今回は、Pythonの外部コマンド実行について解説しました。

以上、参考になれば幸いです。

コメント

タイトルとURLをコピーしました