コマンドラインスクリプトを作る時は、引数やパラメータをどのように処理する考えないといけません。主な理由は、特定な値をスクリプトの中にハードコートすることは避けたいからです。ディレクトリをまとめて削除するスクリプトを例に取ってみましょう。普通は次のように実行したいことでしょう

./delete-dirs.sh dir1 dir2 dir3 ...

このようにすれば、任意のディレクトリを削除することが可能です。ディレクトリ名をスクリプトの中にハードコードしてしまうと、別のディレクトリを消したい場合は、毎回スクリプトを編集しなければならなくなってしまいます。

上の例では、消したいディレクトリの数は3つでした。一般的には、スクリプトへの引数は、値のリストとして渡されます。一つ目の引数へアクセスする時は $1 2つ目は $2 といった具合です。数が決まっていない場合のために、 bash では $@ という変数が用意されています。ここではとてもシンプルです。しかし、例えば任意の数の引数に加えて、省略可能なフラグの処理を追加したい場合はどうすれば良いでしょうか?

次の例を見て下さい。

./delete-dirs.sh --confirm dir2 dir2 dir3 ...

それぞれの引数について、まず前に -- が付いているかチェックし、もしそうであれば処理を分ける必要があります。ここまで来ると、コマンドラインのパーサを書く必要性が理解できるでしょう。

大部分のUnixライクな環境には getoptsという小さなコマンドがあります。また、以前にはよく似たようなコマンドgetoptというものもありましたが、(2つの違いに気が付きましたか?)今となっては、getoptsだけがあったことにして、getoptがかつて存在していたことは忘れてしまっても良いでしょう。

getoptsgetoptより良くなったとは言え、コマンドラインパーサを書くのは、あまり楽しい作業ではありません。getoptsを使った例は次のようになります。

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

詳しくは Stackoverflow を参照してください。

ここまでくると、伝統的な getopts のようなツールをつかって、コマンドライン引数を解析するのは、全くもって、面白い作業ではないことがわかります。私は最近はシェル スクリプトを書く機会があまりないので、Python でコマンドライン引数を解析する方法に興味を持つようになり、Pythonには getopt というモジュールがあることがわかりました。これは Unix のツールをよりは少しだけ使いやすいので、興味があれば試してみて下さい。

getoptモジュール以外にも、Pythonにはoptparseというモジュールがあります。optparseを使ってコマンドライン引数を解析すると、以下のようになります(docsから引用)

from optparse import OptionParser
[...]
parser = OptionParser()
parser.add_option("-f", "--file", dest="filename",
                  help="write report to FILE", metavar="FILE")
parser.add_option("-q", "--quiet",
                  action="store_false", dest="verbose", default=True,
                  help="don't print status messages to stdout")

(options, args) = parser.parse_args()

オプションパラメータ fileoptions.fileで、-qパラメータはoptions.quiet(デフォルトの値はFalse)でアクセスすることが可能です。これでずっと良くなりましたが、optparseは Python 3.2 から非推奨となりましたので、現在では有効な解決手段ではありません。Python 3 から新しいモジュール argparse が導入されています。 optparseargparseのどちらがが良いかについては、議論があるのですが、ここではそこには立ち入りません。

ここまできて分かるのは、コマンドラインプログラムを、正しくつくるにはgetoptoptparseargparseよりもずっと高レベルのツールが必要だということです。 そこでコマンドラインフレームワークの登場です。次のような Python プログラムを考えてみてください。

def do_something(param1, param2, flag=False):
    pass

if __name__ == '__main__':
    make_command(do_something)

そして、それが魔法のように動きます。

./myprogram param1 param2
./myprogram param1 param2 --flag
./myprogram --help
Usage: <myprogram> [options]

Options:
  -h, --help                show this help message and exit
  -f FLAG, --flag=Boolean   Default to False

もし、スクリプトが2つ以上の機能をもつときは

def do_something(param1, param2, flag=False):
    pass

def do_another_thing(param1):
    pass

if __name__ == '__main__':
    make_command(do_something, do_another_thing)

そして、そのプログラムは次のように使えたら

./myprogram do_something param1 param2 --flag
./myprogram do_another_thing param1

とても凄いことだと思いませんか?

私は、ほとんどの場合、Bakerというパッケージを使っています。これはPyPIから入手可能です。これは現在では最良の選択ではないかもしれませんが、私がこのようなツールを探していた時点(7年前)では、やりたい事にもっとも適したツールと思えました。もし、今日の時点で探すなら、clickを選ぶかもしれません。

Googleも似たようなツールFireを持っています。これはしばらく使ってみましたが、Bakerより強力であり、Bakerが持っていたいくつかの問題点が解決されていました。しかし、コマンドの戻り値が自動的に表示されてしまい、またその機能を無効化するオプションが無いなど、少しやり過ぎの面も感じました。

世の中には、もっと沢山の Python ツールがあります。"python cli tools"でググってみれば、ここでで触れなかったツールがもっと見つかるでしょう。Pythonスクリプトを書くときに、このようなツールの必要性がわかって頂ければ嬉しいです。

カマル・ムスタファ

カマル・ムスタファ

最高技術責任者

2010年12月に入社。2000年からASPのウェブ開発をはじめ、PHPなどの開発を経験し当社に入ってからDjango/Pythonの開発をメインにしています。仕事以外では、githubにていろんなオープンソースプロジェクトをハッキングしています。