ちょっとしたツールのためにPythonからGitやDockerなどのコマンドを実行してゴニョゴニョする、ということはよくあるかと思います。
OSコマンドを手軽に実行するPython標準ライブラリ subprocess で頻用する機能について使い方を整理します。
環境
$ python --version Python 3.6.8
subprocess
Python上から他のプログラム (コマンド) を別のプロセスで実行することができる標準ライブラリです。
使い方はシンプルで subprocess.run(["実行したいコマンド", "オプションなど", ...])
でOKです。
- 結果はデフォルトでは標準出力に表示されます (= printを実行したときと同じ)
import subprocess subprocess.run(["touch", "hoge.txt"]) subprocess.run(["ls"]) # hoge.txt
CompletedProcess
runメソッドの返り値はCompletedProcessで、ステータスコード (returncode) 、標準出力 (stdout) 、エラー出力 (stderr)などをプロパティで持ってます。
- Pythonインタプリタの標準出力に流れてますので、stdoutはNoneとなります
result = subprocess.run("ls") print(result.returncode) # 0 print(result.stdout) # None
結果を文字列で取得する
実行結果を文字列で取得したい場合、stdout=subprocess.PIPE
でrunする必要があります。これによって標準出力がCompletedProcessのstdoutへパイプされます。
- runに
encoding=utf-8
を渡すとデコードも文字列で取得できます - 全く出力が不要なら
stdout=subprocess.DEVNULL
で捨てることもできます
result = subprocess.run(["ls"], stdout=subprocess.PIPE) print(result.stdout.decode("utf-8")) # print(result.stdout)の出力はバイナリなのでデコードしてます # fuga.txt # hoge.txt #
出力だけほしいならsubprocess.getoutputメソッドを使うこともできます。こっちのほうがシンプルです。
output = subprocess.getoutput("ls") print(output) # fuga.txt # hoge.txt
2020/1/27 追記
Python 3.7以降であれば、text=True
でも文字列でゲットできます。1
result = subprocess.run(["ls"], stdout=subprocess.PIPE, text=True) print(result.stdout) # fuga.txt # hoge.txt #
result = subprocess.run(["ls"], stdout=subprocess.PIPE, )
エラーハンドリング
実行に失敗した場合も、同様にCompletedProcessが返されます。returncodeやstderrなどのプロパティでその後の処理をハンドリングします。
result = subprocess.run(["ls", "-"], stderr=subprocess.PIPE) print(result.returncode) # 1 print(result.stderr.decode("utf-8")) # ls: -: No such file or directory #
check=True
としておくと、0以外のリターンコードの場合に例外 subprocess.CallProcessError がスローされます。エラー時には強制終了させたいなどのケースにはうってつけです。
result = subprocess.run(["ls", "-"], check=True) # ls: -: No such file or directory # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # File "/Users/ohke/.pyenv/versions/3.6.8/lib/python3.6/subprocess.py", line 438, in run # output=stdout, stderr=stderr) # subprocess.CalledProcessError: Command '['ls', '-']' returned non-zero exit status 1.
タイムアウト
timeout引数に秒数を渡すことでタイムアウトも制御できます。タイムアウトした場合、subprocess.TimeoutExpiredがスローされます。
result = subprocess.run(["sleep", "10"], timeout=2) # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # File "/Users/ohke/.pyenv/versions/3.6.8/lib/python3.6/subprocess.py", line 430, in run # stderr=stderr) # subprocess.TimeoutExpired: Command '['sleep', '10']' timed out after 2 seconds
シェル
shell=True
とするとシェルで実行されます。このとき、第1引数はリストではなく文字列を渡すことができます。実用上はリストを作るのがめんどくさいからシェルに渡す、というケースが多いかと思います。
- コマンドインジェクションなどのリスクは伴います
subprocess.run("mkdir -p dir1", shell=True)
カレントディレクトリを移動する
cwd引数にパスを渡すと、カレントディレクトリを変更してコマンドを実行できます。
subprocess.run(["touch", "./file1.txt"], cwd="./dir1") subprocess.run(["ls", "./dir1/file1.txt"]) # ./dir1/file1.txt