Help us understand the problem. What is going on with this article?

pythonからterminalを操作する (subprocess)

More than 1 year has passed since last update.

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コマンドを実行する

Linux_terminal
ls
Windows_コマンドプロンプト
dir

上記のようなコマンドはsubprocess.run()で以下のように実行できます。

Linuxの場合
import subprocess
subprocess.run('ls')
Windowsの場合
import subprocess
subprocess.run('dir', shell=True)

terminalに表示が出るコマンドなら、表示を文字列の返り値として受け取れます。文字列なので正規分布で必要な部分だけ取り出すようなことも出来ます。

Linuxの場合
import subprocess
result = subprocess.run('ls')
print(result)
Windowsの場合
import subprocess
result = subprocess.run('dir', shell=True)
print(result)

terminalコマンドに引数を与えて実行する

引数を与える場合、terminalではスペース区切りでしたがsubprocessではlistにして渡します。

python
result = subprocess.run(['ls', '-l'])
print(result)

複数引数がある場合はlistが長くなっていくだけです。

python
result = subprocess.run(['ls', '-l', '-a'])
print(result)

ちなみに1個だけにして実行することも出来ます。引数があったりなかったりするときはこちらの書き方の方がやりやすそう。

python
result = subprocess.run(['ls'])
print(result)

シェルスクリプト(バッチファイル)を実行する

terminalコマンドはなんでも実行できるので、当然シェルスクリプトも実行できますし引数も渡せます。以下では引数2つで実行してます。

Linuxの場合
result = subprocess.run(['hoge.sh', 'value1', 'value2'])
print(result)
Winの場合
result = subprocess.run(['hoge.bat', 'value1', 'value2'])
print(result)

pythonコードを実行する

terminalコマンドが実行できるという事は、pythonインストール済み環境ならpythonも実行できます。以下ではhoge.pyに引数を2つ渡して実行しています。

python
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になるので、以下の書き方で実行できます。

Windowsの場合
subprocess.run(['E:/Anaconda3/envs/tf/python.exe', 'hoge.py'])
Linuxの場合(追記予定)

なお、pythonインタプリタがどこにいるのか調べるのはterminalでconda activateしておいてwhere/whichすれば簡単です。

Windowsの場合
conda activate tf
where tf
Linuxの場合
conda activate tf
which tf

gpuメモリ残量を確認する

nvidia-smiが使える環境なら受け取った文字列をparseしてGPU使用量も取得できるかな、と思っていたらもうやっている人がいました。
https://qiita.com/tomotaka_ito/items/1da001c98b46ecf28ec7

python
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またはコマンドプロンプトを開くことができます。

Linuxの場合
gnome-terminal ls -l
Windowsの場合
start dir /w

上記のコマンドは以下のように書けるのでpythonから実行することが出来ます。

Linuxの場合
subprocess.run(['gnome-terminal', 'ls', '-l'])
Windowsの場合
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についての説明を追記しました。

studio_haneya
製造業でデータサイエンティスト的な仕事をやってます
https://twitter.com/studio_haneya
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away