Pythonでのオプションパースのおはなし

私は仕事でよくPythonで小さなツールを作成します。ログ解析やログ収集、 日報の加工や共有サーバの自分のアカウントのバックアップ等々。 その際、ツールにコマンドオプションを渡すことはよくあるシチュエーションです。 今日はPythonでのオプションパースのお話を少し。

例として、-tオプションで時刻表示、-vでバージョン表示、 その他文字列指定でファイル内容を表示する(catコマンドみたいな感じ)、 コマンドを作成してみます。

初心者またはものぐさな人向け(sys.argv)

コマンド引数にアクセスできるsys.argvを直接解析すれば 簡易のオプションパーサを作れます。(これをオプションパーサとは言わない気がするが)

#!/usr/bin/env python
__version__ = '0.0.1'
import sys
import time

def usage():
    print "usage: magic [OPTION]"
    print "[OPTION]"
    print " -h : just now"
    print " -t : time print"
    print " -v : version print"
    print ""

if __name__ == '__main__':
    if len(sys.argv) == 1:
        sys.exit()

    if sys.argv[1] == '-h':
        usage()
    elif sys.argv[1] == '-v':
        print "this tool's version is %s" % __version__
    elif sys.argv[1] == '-t':
        print time.ctime()
    else:
        print open(sys.argv[1]).read()

煩雑すぎて、逆に初心者にはお奨めできないかも。 sys.argvを直接解析していくのは、一撃必中ツール (オプションが少ない時やツールを作った人しか使わない時)に 限られると思います。 より現実的な方法としては、getoptモジュールやoptparseモジュール を使う方法があります。

古参プログラマ向け(getopt)

getoptモジュール は、UnixのgetoptコマンドまたはC標準関数のgetopt()に 似たインターフェースを提供します。

例を示します。

#!/usr/bin/env python
__version__ = '0.0.1'
import getopt
import sys
import time

def usage():
    print "usage: magic [OPTION]"
    print "[OPTION]"
    print " -h : just now"
    print " -t : time print"
    print " -v : version print"
    print ""
    sys.exit()

if __name__ == '__main__':
    try:
        shortopt = "hvt"
        longopt = ["help", "version", "time"]
        opts, args = getopt.getopt(sys.argv[1:], shortopt, longopt)
    except getopt.GetoptError:
        usage()

    for o, a in opts:
        if o in ('-v', '--version'):
            print "this tool's version is %s" % __version__
        elif o in ('-h', '--help'):
            usage()
        elif o in ('-t', '--time'):
            print time.ctime()

    for f in args:
        print open(f).read()

getoptを使うのは開発環境のPythonのバージョンが古い等で optparseモジュールが使えない場面くらいでしょうか。 optparseが使えるのであればgetoptモジュールを使う必要性はあまりないと思います。

ニュータイプ向け(optparse)

より高機能なオプションパーサとして、 optparseモジュール が用意されています。 バージョン2.3から使えます。

例を示します。

#!/usr/bin/env python
__version__ = '0.0.1'
from optparse import OptionParser
import time

if __name__ == '__main__':
    p = OptionParser(version="ver:%s" % __version__)
    p.add_option('-t', '--time', action='store_true',
                 help="time print just now.")

    opts, args = p.parse_args()

    if opts.time:
        print time.ctime()
    for f in args:
        print "file: ", f
        print open(f).read()

これからのPythonistaはoptparseモジュールを使うべきです。 ヘルプ表示生成の容易さやパーサ拡張も行えます。

一歩先へ(optcompleteを使う)

optparseモジュールを便利に使うことができるエクステンションモジュールとして optcompleteモジュール があります。

optcompleteモジュールはoptparseモジュールで作成したパーサをもとに、 bashコンプリーションを便利に使うことができます。

まずはインストール。

$ wget http://furius.ca/downloads/optcomplete/releases/optcomplete-1.2.tar.bz2
$ tar xjf optcomplete-1.2.tar.bz2
$ cd optcomplete-1.2
$ python setup.py install
$ cp -p etc/optcomplete.bash /etc/bash_complete.d/optcomplete

では、先ほどのoptparseの例をもとにoptcompleteを使ってみます。

#!/usr/bin/env python
__version__ = '0.0.2'
from optparse import OptionParser
import time
import optcomplete

#optcomplete.debugfn = '/tmp/optcomplete.log'

if __name__ == '__main__':
    p = OptionParser(version="ver:%s" % __version__)
    p.add_option('-t', '--time', action='store_true',
                 help="time print just now.")

    optcomplete.autocomplete(p)

    opts, args = p.parse_args()
    if opts.time:
        print time.ctime()
    for f in args:
        print "file: ", f
        print open(f).read()

使い方としては、parse_args()をコールする前に、 optcomplete.autocomplete(parser-object)をコールします。 使い方としてはこれだけです。 optcomplete.debugfn で動作ログの出力先を指定できます。

実際に使ってみると以下のようにファイル名の補完とともに コマンドオプションも補完してくれるようになります。 completeコマンドはログインの度に打つ必要があるので、 $HOME/.bashrcファイルあたりに追記しておくとよいと思います。

$ cp optcomp-sample.py /usr/local/bin/optcamp-sample
$ chmod 755 /usr/local/bin/optcamp-sample
$ complete -F _optcomplete optcomp-sample
$ optcomp-sample
--help              -h                  optcomp-sample.py
--time              -t                  optparse-sample.py
--version           getopt-sample.py    sysargv.py
$

最後に

その他の気になるエクステンションモジュールとしては、 CmDOoptikmandysimpleopt あたりでしょうか。 次回紹介できる機会があれば紹介したいと思います。