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

 

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