【subprocess】Pythonでコマンドを実行する方法

2021年1月3日日曜日

Mac Python Python3 便利

B!

subprocessとは

pythonでコマンド(MacにおけるターミナルやWindowsにコマンドプロンプトで実行するもの)を実行するためのパッケージです.詳しく説明してくれているサイトもなかったのでいろいろ試してみました.

公式ドキュメントはこちら↓

基本的な使い方

先にpythonの実行環境を示します.

In [1]:
import sys
print(sys.version)
3.7.9 | packaged by conda-forge | (default, Dec  9 2020, 20:58:55) 
[Clang 11.0.0 ]

使い方としては,subprocess.run('command')または,subprocess.run(['command', 'option1', 'option2'])などのように書きます.
たとえば,macの実行環境を出力するsw_versコマンドをpythonから呼び出したいとします.(sw_versコマンドに関しては以下のサイト参照)

In [2]:
import subprocess
subprocess.run('sw_vers')
Out[2]:
CompletedProcess(args='sw_vers', returncode=0)

以上のようにして,そのコマンドの文字列を与えることで実行することができます.
しかし以上のコードでは,確かに実行されているのですが,これでは結果がどこにも見れません.

結果の出力先をstdoutという引数に指定することで出力することができます.

結果を標準出力したい場合

標準出力とはターミナル(コマンドプロンプト)上に表示させる,ということです.
それをしたいとき,stdoutに,subprocess.PIPEを指定します.こうすることでその文字列をpythonの変数で受け取ることもできます. たとえば,以下のようにします.

In [3]:
process = subprocess.run('sw_vers', stdout = subprocess.PIPE)
print(process.stdout.decode())  # バイトで得られるのでdecode()した.
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H15

デフォルトではstdoutはbyteで得られてしまうのでこれを任意の文字コードで出力するためには,以上のようにdecodeしてあげるか,引数encodingを指定します.

In [4]:
process = subprocess.run('sw_vers', stdout = subprocess.PIPE, encoding = 'utf_8_sig')
print(process.stdout)  # バイトで得られるのでdecode()した.
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H15

また,先に示した通りcommandの他にoptionを与えることができます.

たとえばsw_versを実行する際のオプションとしてProductVersionだけを取得するoptionである-productVersionというものがあります.これを実行するためには以下のようにリストで与えます.

In [5]:
product_version = subprocess.run(['sw_vers', '-productVersion'], stdout = subprocess.PIPE, encoding = 'utf_8_sig').stdout
print(product_version)
10.15.7

やりがちなミスとしてリストではなく,そのまま引数として加えるというミスをやりがちです.その場合エラーコードは以下のようになるので参考にしてください.

In [6]:
subprocess.run('sw_vers', '-productVersion', stdout = subprocess.PIPE, encoding = 'utf_8_sig')

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-8e7a838b7002> in <module>
----> 1 subprocess.run('sw_vers', '-productVersion', stdout = subprocess.PIPE, encoding = 'utf_8_sig')

~/.pyenv/versions/anaconda3-2019.03/envs/CP/lib/python3.7/subprocess.py in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    486         kwargs['stderr'] = PIPE
    487 
--> 488     with Popen(*popenargs, **kwargs) as process:
    489         try:
    490             stdout, stderr = process.communicate(input, timeout=timeout)

~/.pyenv/versions/anaconda3-2019.03/envs/CP/lib/python3.7/subprocess.py in __init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, encoding, errors, text)
    700             bufsize = -1  # Restore default
    701         if not isinstance(bufsize, int):
--> 702             raise TypeError("bufsize must be an integer")
    703 
    704         if _mswindows:

TypeError: bufsize must be an integer

結果をファイルとして書き出したい場合

その場合は,ファイルオブジェクトを渡します.
たとえばこのmacの実行条件をoutput.txtというファイルに書き出したいと思った場合以下のように書くことができます.

In [7]:
with open('output.txt', mode = 'w') as f:
    subprocess.run('sw_vers', stdout = f, encoding = 'utf_8_sig')

ファイルオブジェクトの取り扱い方法はいくつかあるのですが,一番簡便なのはこの書き方だと個人的には思っているのでこちらのやり方で紹介しています.

その他のやり方が興味がある人は是非調べてみてください.参考になるかもしれないサイト↓

より実践的な実行例

以上のやり方を応用すればanacondaの仮想環境を書き出したりすることができ,再現性を確保するために一役買うことができるかもしれません.
以下のコードで実行できます.

In [8]:
with open('env.yml', mode = 'w') as f:
    subprocess.run(['conda', 'env', 'export'], stdout = f, encoding = 'utf_8_sig')

逆に個別のanacondaの仮想環境を作ったりできますので,使用方法はたくさんあるかも?です.

ちなみにですが,本来ターミナル上でanacondaの仮想環境を書き出す場合

conda env export > env.yml

とするので,

In [9]:
subprocess.run(['conda', 'env', 'export', '>', 'env.yml'])
Out[9]:
CompletedProcess(args=['conda', 'env', 'export', '>', 'env.yml'], returncode=2)

で行けそうな感じがしますが,ファイルが生成されないので無理そうです.

ちなみにsubprocess.runコマンドを実行したときに返ってくるインスタンスCompletedProcessreturncodeの値が0のときは何も問題がなく終了したことを示すので,それ以外の値だったときは何かしら問題が起きているので参考にすると良いかもしれないです.

anacondaの仮想環境情報の書き出しはこちらを参考に↓.

まとめ

  • コマンドのみの実行: subprocess.run('command')←文字列で渡す
  • オプションをつける場合: subprocess.run(['command', 'option1', 'option2', ...])←リストで渡す
  • 出力させたい場合
    • 標準出力: subprocess.run('command', stdout = subprocess.PIPE)
    • ファイル出力: subprocess.run('command', stdout = f)fはファイルオブジェクト

参考文献


自己紹介

自分の写真
国立大学の大学院で応用化学を専攻している学生です.

このブログを検索

人気の投稿

注目の投稿

PythonからIgor Proにデータを渡す方法【IgorWriter】

こんにちは. 現在自分が所属している研究室ではIgor Proを使用しているため,そちらでデータを渡して欲しいと言われることが多々あります. これをどうにかできないかと考え,調べていたところ便...

QooQ