subprocessを使ってpythonからterminalを操作する方法を調べたのでまとめます。
subprocessとは
pythonからterminalを操作する為のpackageです。terminalが操作できるという事はシェルスクリプトでやるような事が一通り出来るわけです。便利すぎてヤバイです。
長年開発されてきているのでcall, check_call, check_outputなど色んなモジュールがあるようですが、普通に実行するときはsubprocess.run()を使い、非同期で実行するときはsubprocess.Popen()を使うよう推奨しているようです。Popenは並列処理用のモジュールなので、子プロセスの終了を待ったりkillしたり出来るようになっています。
公式ドキュメント
https://docs.python.org/ja/3.6/library/subprocess.html#using-the-subprocess-module
今回はすぐ終わる処理しかやらないのでsubprocess.run()を使います。
試行環境
Windows 10
python 3.6
Ubuntu 18.04
python 3.6
terminalコマンドを実行する
ls
dir
上記のようなコマンドはsubprocess.run()で以下のように実行できます。
import subprocess
subprocess.run('ls')
import subprocess
subprocess.run('dir', shell=True)
terminalに表示が出るコマンドなら、表示を文字列の返り値として受け取れます
import subprocess
result = subprocess.run('ls')
print(result)
import subprocess
result = subprocess.run('dir', shell=True)
print(result)
terminalコマンドに引数を与えて実行する
引数を与える場合、terminalではスペース区切りでしたがsubprocessではlistにして渡します。
result = subprocess.run(['ls', '-l'])
print(result)
複数引数がある場合はlistが長くなっていくだけです。
result = subprocess.run(['ls', '-l', '-a'])
print(result)
ちなみに1個だけにして実行することも出来ます。引数があったりなかったりするときはこちらの書き方の方がやりやすそう。
result = subprocess.run(['ls'])
print(result)
シェルスクリプト(バッチファイル)を実行する
terminalコマンドはなんでも実行できるので、当然シェルスクリプトも実行できますし引数も渡せます。以下では引数2つで実行してます。
result = subprocess.run(['hoge.sh', 'value1', 'value2'])
print(result)
result = subprocess.run(['hoge.bat', 'value1', 'value2'])
print(result)
pythonコードを実行する
terminalコマンドが実行できるという事は、pythonインストール済み環境ならpythonも実行できます。以下ではhoge.pyに引数を2つ渡して実行しています。
subprocess.run(['python', 'hoge.py', 'value1', 'value2'])
conda環境を指定してpythonコードを実行する
pythonインタプリタは実行ファイルなので、PATHを指定してpythonを実行すればいま動作してる環境とは別のconda環境のpythonも使えます。例えばWindows環境でE:/Anaconda3/にインストールしたconda環境のtfはE:/Anaconda3/envs/tf/python.exeになるので、以下の書き方で実行できます。
subprocess.run(['E:/Anaconda3/envs/tf/python.exe', 'hoge.py'])
なお、pythonインタプリタがどこにいるのか調べるのはterminalでconda activateしておいてwhere/whichすれば簡単です。
conda activate tf
where tf
conda activate tf
which tf
gpuメモリ残量を確認する
nvidia-smiが使える環境なら受け取った文字列をparseしてGPU使用量も取得できるかな、と思っていたらもうやっている人がいました。
https://qiita.com/tomotaka_ito/items/1da001c98b46ecf28ec7
import subprocess
import json
DEFAULT_ATTRIBUTES = (
'index',
'uuid',
'name',
'timestamp',
'memory.total',
'memory.free',
'memory.used',
'utilization.gpu',
'utilization.memory'
)
def get_gpu_info(nvidia_smi_path='nvidia-smi', keys=DEFAULT_ATTRIBUTES, no_units=True):
nu_opt = '' if not no_units else ',nounits'
cmd = '%s --query-gpu=%s --format=csv,noheader%s' % (nvidia_smi_path, ','.join(keys), nu_opt)
output = subprocess.check_output(cmd, shell=True)
lines = output.decode().split('\n')
lines = [ line.strip() for line in lines if line.strip() != '' ]
return [ { k: v for k, v in zip(keys, line.split(', ')) } for line in lines ]
if __name__ == "__main__":
import pprint
pprint.pprint(get_gpu_info())
新しくterminalを開いてコマンドを実行する
Linuxだとgnome-terminalコマンドで、Windowsだとstartコマンドを使うと新たにterminalまたはコマンドプロンプトを開くことができます。
gnome-terminal ls -l
start dir /w
上記のコマンドは以下のように書けるのでpythonから実行することが出来ます。
subprocess.run(['gnome-terminal', 'ls', '-l'])
subprocess.run(['start', 'dir', '/w'], shell=True)
長時間かかるプロセスならこちらの方がモニターしやすいかもしれません。
shell=Trueについて
shell=Trueにするとシェルを使って実行します。シェルの埋め込みコマンド(Windowsのdirとかstartとか)はシェルからしか呼べないのでshell=Trueにして使います。でもシェルで実行するという事は何でもできてしまうという事なので脆弱性の原因になってしまいます。ファイルを実行するような場合はシェルで実行する必要がないのでshell=Falseにしておきます。デフォルトもshell=Falseですから省略すればOKです。
詳しくはこちら
https://docs.python.org/ja/3.6/library/subprocess.html#security-considerations
また、shell=Trueにすると引数の与え方が変わります。Linuxの場合、shell=Falseでは引数をlistで与えますが、shell=Trueだとスペース区切りの文字列で引数を与えるように設定されます。うっかりlistで与えると2番目以降が無視されちゃって意図しない動作をしてしまったりしますので注意が必要です。何故かエラーも出してくれないので気付くことができません。Windowsの場合、shell=Trueにするとスペース区切りが使えるようになるのは同じなんですが、shell=Trueでもlistを正常に受け取れちゃいます。stdioの挙動も違ってくるようなので色々注意が必要です。
公式ドキュメントに記述があります
https://docs.python.org/ja/3.6/library/subprocess.html#popen-constructor
結論
- subprocessマジで便利
- 脆弱性には気を付けよう
- レッツトライ
修正履歴
20190903: shell=Trueが不要なところについたり要るところに付いてなかったりしてたのを修正しました。shell=Trueについての説明を追記しました。