MyEnigma

とある自律移動システムエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

Pythonでコマンドラインツールを作る方法

目次

はじめに

データ解析の時など、

コマンドラインツールを作りたくなることがあります。

 

自分だけが、その時だけ使う場合は、

適当にスクリプトファイルを作れば良いですが、

他人に使ってもらったり、

長い期間使用して、メンテする場合は、

ちゃんとコマンドラインツールとして作っておくと

コードの寿命が長くなると思います。

 

pythonにはそのような

自作コマンドラインツールを作るツールなどが、

豊富に準備されているので、

今回はそれらのツールを使った

pythonコマンドラインツールの作り方について説明したいと思います。

コマンドラインオプションツール argparseの使い方

pythonのデフォルトモジュールである

argparseは、コマンドラインツールに

重要なコマンドラインオプションのパーサモジュールです。

unixのツールによくあるハイフン+記号のオプション

のようなオプション指定のシステムと、

その使い方を示したヘルプを自動生成してくれます。

argparseのサンプルプログラム

下記はargparseのサンプルプログラムです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-:q

import argparse

parser = argparse.ArgumentParser(description='argparse sample.')

#bool オプション
parser.add_argument('-e','--error', action='store_true', default=False, help='show error (default: show no error)')
#数値 オプション
parser.add_argument('-d','--data', type=int, help='data number')
#文字列オプション
parser.add_argument('-s','--str', type=str, help='data name')

args = parser.parse_args()

print args

argparseは大きく分けて3つの処理に分けられれるため、

それぞれの処理について説明します。

1 パーサの生成

まずはじめに下記のように、argparseをインポートして、

コマンドラインオプション用のパーサを作ります。

引数のdescriptionは、後述するヘルプ表示をした時の、

スクリプトの概要説明文になります。

import argparse

parser = argparse.ArgumentParser(description='argparse sample.')

2 コマンドラインオプションの追加

続いて、使用するコマンドラインオプションを追加します。

boolや数値、文字列など、基本的なデータを

コマンドラインオプション経由で指定することができます。

bool型のオプション

下記のように設定することで、bool型のオプションを設定できます。

parser.add_argument('-e','--error', action='store_true', default=False, help='show error (default: show no error)')

上記のコードの場合、-eを付けることでerrorという変数をTrueにすることができます。

-eをつけた時に、errorをFalseにしたい場合は、action='store_false'とします。

defaultはオプションを指定しなかった時のerrorの値です。

helpは後述のようにヘルプを表示させた時の、

オプションの説明になります。

 

下記のようにオプションを指定することで

error変数がTrueになっているのがわかります。

$ python argparseSample.py -e

Namespace(data=None, error=True, str=None)

数値型のオプション

下記は数値オプションです。

parser.add_argument('-d','--data', type=int, help='data number')

type引数に型を指定することで、

下記のように型以外の引数を与えた時にエラーを返してくれます。

$ python argparseSample.py -d 20.0

usage: argparseSample.py [-h] [-e] [-d DATA]

文字列型のオプション

最後がコマンドラインオプションのパース文です。

parser.add_argument('-s','--str', type=str, help='data name')

基本的に先ほどの数値と同じです。

下記のように使用できます。

$ python argparseSample.py -s hoge

Namespace(data=None, error=False, str='hoge')

3. コマンドラインオプションのパース

最後は受け取ったコマンドラインオプションをパースします。

args = parser.parse_args()

args変数の要素として、

args.errorやargs.data、args.strが格納され、

コードに使用することができます。

ヘルプの表示

argparseの素晴らしい所は

コマンドラインツールのヘルプを自動生成してくれる所です。

先ほどのサンプルコードを-hのオプションを指定すると、

下記のようにヘルプが表示されます。

$python argparseSample.py -h

usage: argparseSample.py [-h] [-e] [-d DATA] [-s STR]

argparse sample.

optional arguments:

-h, --help show this help message and exit

-e, --error show error (default: show no error)

-d DATA, --data DATA data number

-s STR, --str STR data name

コードをドキュメント化出来て、

すごく便利ですね。

 

コマンドラインツール作成用ライブラリClick

argparseはデフォルトでPythonにバンドルされているので、

使いやすいですが、Clickというライブラリを使いことで、

簡単にデコレータを使って、関数をコマンドラインツール化できます。

 

click.palletsprojects.com

github.com

blog.amedama.jp

qiita.com

 

下記の記事のように、setup.pyを設定することで簡単に

pipでインストールするようにパッケージ化が可能です。

click.palletsprojects.com

こちらのリポジトリは、非常に簡単なサブコマンドを持つCLIツールのサンプルです。

github.com

 

下記のように、シェル補完も可能です。

qiita.com

 

こちらの3rd partyライブラリを使うと、

コマンドラインからロガーのレベルを変更することができます。

click-log.readthedocs.io

f:id:meison_amsl:20211024222503p:plain

 

clickで作ったツールのテスト方法

venvを使って、仮想環境を作ることでかんたんにテストできます。

$ python -m venv venv

$ source venv/bin/activate

$ pip install --editable .

 

Pythonからシェルコマンドを実行する方法

コマンドラインツールを作っていると、

すべてpythonでやるのではなく、

シェルコマンドを使って、

ファイルの検索やコピー、削除などをしたくなります。

そこで自作のpythonコードからシェルコマンドを駆使する方法を説明します。

 

pythonからシェルコマンドを利用する場合は、

osモジュールを使う方法がかつては一般的でしたが、

osモジュールは将来的に廃止される予定らしいので、

現在はsubprocessというモジュールを使います。

このsubprcessは新しいプロセスを生成して処理を実行する

pythonのデフォルトモジュールです。

subprocessを使ってシェルコマンドを実行する方法は

下記のようにいくつかあります。

call関数 シェルコマンドを実行する

シェルコマンドを実行するのみなら、

call関数が便利です。

引数にシェルコマンドを文字列として指定し、

shell=Trueとすればコマンドを実行してくれます。

コマンドが終了するまで待つようになるので注意しましょう。

import subprocess
cmd = "ls"
subprocess.call( cmd, shell=True  ) 

check_output関数 シェルコマンドの返り値を利用する

シェルコマンドの返り値を利用したい場合は、

check_output関数を使います。

import subprocess
cmd = "ls"
ret=subprocess.check_output( cmd, shell=True  ) 
print ret

check_call関数 シェルコマンドの終了を待つ

シェルコマンドの処理が終わったことをチェックしたい場合は

check_call関数を使います。

無事コマンドが終了すれば、返り値は0になるはずです。

エラーの場合は、CalledProcessError例外が出されます。

import subprocess
cmd = "ls"
ret  =  subprocess.check_call( cmd )
print ret

 

Popen:プロセスの終了を待たずに複数処理を並列実行する

上記のサンプルでは、

一つのプロセスが終わるまで

次の処理を行えませんでしたが、

下記の記事でかいたとおり、

Popenを使うことで、

別プロセスで処理を実行させつつ、

別の処理を実施することができます。

myenigma.hatenablog.com

 

例えば重い処理をマルチコアの有効性を利用して

同時処理したい場合は、

下記のサンプルのように、

Popenを使って複数の処理を並列実行することができます。

import subprocess
subprocess.Popen("hoge 1",shell=True)
subprocess.Popen("hoge 2",shell=True)
subprocess.Popen("hoge 3",shell=True)

 

シェルの標準出力に色を付けて文字列を出力する方法

f:id:meison_amsl:20160228210947p:plain

 

コマンドラインツールを作っていると、

エラーやワーニングの情報をユーザに伝えるために、

出力結果の文字に色を付けたくなることがあります。

そんな時は、下記のようにprint文の冒頭と末尾に

フォーマッティングの文字を挿入すれば、

文字に色を付けることができます。

 

しかし、上記のようにフォーマットをするのは面倒なので、

簡単に文字列に色を付けて文字を標準出力できる

モジュールを作りました。

 

下記のモジュールを保存して、

Print関数を使うことで、

冒頭のような色付きの文字列を表示させることができます。

使い方は、main文の中をみればすぐにわかると思います。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Atsushi Sakai
# license: MIT

def Print(string, color, highlight=False):
    u"""
    Colored print

    colorlist:
        red,green,yellow,blue,magenta,cyan,white,crimson

    """
    end="\033[1;m"
    pstr=""
    if color == "red":
        if highlight:
            pstr+='\033[1;41m'
        else:
            pstr+='\033[1;31m'
    elif color == "green":
        if highlight:
            pstr+='\033[1;42m'
        else:
            pstr+='\033[1;32m'
    elif color == "yellow":
        if highlight:
            pstr+='\033[1;43m'
        else:
            pstr+='\033[1;33m'
    elif color == "blue":
        if highlight:
            pstr+='\033[1;44m'
        else:
            pstr+='\033[1;34m'
    elif color == "magenta":
        if highlight:
            pstr+='\033[1;45m'
        else:
            pstr+='\033[1;35m'
    elif color == "cyan":
        if highlight:
            pstr+='\033[1;46m'
        else:
            pstr+='\033[1;36m'
    elif color == "white":
        if highlight:
            pstr+='\033[1;47m'
        else:
            pstr+='\033[1;37m'
    elif color == "crimson":
        if highlight:
            pstr+='\033[1;48m'
        else:
            pstr+='\033[1;38m'
    else:
        print("Error Unsupported color:"+color)

    print(pstr+string+end)

if __name__ == '__main__':
    Print("Red","red")
    Print("Green","green")
    Print("Yellow","yellow")
    Print("Blue","blue")
    Print("Magenta","magenta")
    Print("Cyan","cyan")
    Print("White","white")
    Print("Crimson","crimson")
    Print("Highlited Red","red",highlight=True)
    Print("Highlited Green","green",highlight=True)
    Print("Highlited Yellow","yellow",highlight=True)
    Print("Highlited blue","blue",highlight=True)
    Print("Highlited Magenta","magenta",highlight=True)
    Print("Highlited Cyan","cyan",highlight=True)
    Print("Highlited Crimson","crimson",highlight=True)

 

参考資料

kohkimakimoto.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

MyEnigma Supporters

もしこの記事が参考になり、

ブログをサポートしたいと思われた方は、

こちらからよろしくお願いします。

myenigma.hatenablog.com