M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Apache Commons CLI 1.2を使ってみる

【NOTE】 この記事はApache Commons CLI のバージョン1.2について記述しています。2015年5月時点の最新版であるバージョン1.3ではライブラリが提供するインターフェースに大きな変更がありました。この点についてはこちらの記事で若干言及しています。

Javaコマンドライン・アプリを作成する場合、コマンドライン引数とオプションの機能を実現するためにいくつかのライブラリを利用できる*1

今回はそのうちの1つであるApache Commons CLIを使ってみる*2

使用したのは最新版のバージョン1.2。こちらからjarファイルをダウンロードできる。

なお公式サイトで公開されているチュートリアルについてはこちらで訳出している(2013/03/12追記)。

コマンドライン・オプションを定義する

作成するJavaコマンドライン・アプリがどんなオプションをとるかは、Optionsオブジェクトを使って定義する。

Options options = new Options();

Optionsオブジェクトを初期化する方法は2通りあって、1つはOptionsに直接オプションを追加していく方法。このために以下のメソッドが用意されている*3

public Options addOption(String opt, 
                         String longOpt, 
                         boolean hasArg, 
                         String description) {/* ... */}

もう1つの方法は、いわゆる“流れるようなインターフェース”(Fluent Interface)を使ってOptionオブジェクトを作成して、これをOptionsオブジェクトに追加していく方法。

Options options = new Options();
Option logfile   = OptionBuilder.withArgName( "file" )
                                .hasArg()
                                .withDescription(  "use given file for log" )
                                .create( "logfile" );

options.addOption(logfile);

2つ目の方法のほうが各オプションの挙動を詳細に設定できるし、“流れるようなインターフェース”のおかげでコードの可読性は高い。

ただし上記の(OptionBuilderの)各種メソッドがクラスメソッドであるのに対して、これらメソッドが返すのはOptionBuilderインスタンス。よって、上記コードは"static-access"警告の対象になる。

ここで「まぁ言いたいことは分かるけど可読性の方が重要」とか「コードのシンプルさの方が重要」という割り切った考え方で臨むなら @SuppressWarnings("static-access") アノテーションを使って警告出力抑制をしてしまえばいい。
そうでないなら、一度OptionBuilder型変数にインスタンスを受けた後に、各種メソッドを呼び出すなりすればいい。

ところで2つの方法ともに関係してくるOptionの各種設定値について確認。

例として、ここでは作成するコマンドライン・アプリがMyCmdで、-oとか-pとかのオプションをとり、foobarとかの任意のコマンド引数を1つとるとする。-oはオプション引数を1つとる。それから-pはフラグオプションであり、オプション引数はとらない。つまりこんな感じ↓↓↓

java -jar MyCmd.jar -o <option-argument> -p <command-argument>

これに対してOptionBuilderの各種メソッドで示された名称と意味は以下のようになる。

名称 意味
hasArg そのオプションが引数をとるかどうかを決定するもの。例えば"-o"オプションであればこのメソッドを引数なしでコールするか、trueを引数にしてコールすることになる。"-p"オプションであればそもそもこのメソッドをコールしないか、あえてするならばfalseを引数にしてコールする。(int型をとる多重定義メソッドもあって、この場合はオプション引数としてとる値の個数のリミットを設定できる模様。)
isRequired そのオプションが必須のものであるかどうかを決定するもの。hasArgメソッド同様にあえてboolean型の引数を設定することもできる。
withArgName このメソッドはヘルプとか使用方法の表示に関わってくるメタ情報を設定するもので、上記の例で言えば"option-argument"の部分を決定するもの。
withDescription withArgNameと同じくヘルプとか使用方法の表示に関わってくるメタ情報を設定するもの。
withLongOpt "--help"などのようにイニシャルではなくワードを用いて指定する場合の名前を設定するもの。これを設定しない場合どうなるのかよくわからないが、実際のところアプリのユーザのためにもコードの可読性のためにも、設定しておいた方がいいと思う。
create このメソッドによりOption型インスタンスが得られる。例えば'o'とか'p'とかの文字を設定する。前掲の例のように文字列でもよい。

コマンドライン・オプションとコマンドライン引数をパースする

オプションを定義し終えたら、mainメソッドに渡された引数String[]とともにCommandLineParserインターフェース実装のparseメソッドに渡す。Commons CLIプロジェクトで配布しているJarにはあらかじめいくつかのParser実装クラスが含まれている*4

今回は個人的に一番慣れ親しんでいる気がするPOSIXスタイルでいってみる("MyCmd -po baz foobar"みたいにフラグオプションを他のオプションにまとめ込むことができる)。このほかにどんなデフォルト実装が存在するかはここで確認できる。

CommandLine cmd = new PosixParser().parse( options, args);

あとはCommandLineオブジェクトを通じてコマンドライン・オプションとコマンドライン引数を取得する。

名称 意味
getArgs いかなるオプションにも紐付かない引数をString[]として取得できる。つまりコマンドライン・オプションやコマンドライン・オプション引数ではない、コマンドライン引数そのもののみを取得できる。例えばMyCmdの例でいえば"foobar"が格納された配列が返される。
getOptionValue 第1引数にオプション名を指定してオプション引数を取得する。第2引数をとる多重定義メソッドが存在して、こちらは第2引数を設定することで、オプションが設定されていないときのデフォルト値とすることができる。
hasOption オプション名を引数にとって、そのオプションがコマンドラインで入力されたかどうかをチェックできる。例えば"-p"のようなフラグオプションが設定されているかどうかを確認できる。

ちなみにオプション引数をとるオプションであっても、コマンドラインでその引数が指定されなかったからと言って、CLIのレベルで即時エラー、とはならない。このようなとき、getOptionValueメソッドをデフォルト値を与えずコールすると、nullが返ってくる。このときのエラーハンドリングはCLIを使用して開発を行うユーザ=デベロッパに任されている(2013/03/12追記)。

ヘルプ/使用方法を表示する

最後に、ヘルプもしくは使用方法を表示する方法について。

コマンドライン・アプリが引数なしで実行された場合とか、必要なオプションを欠いたかたちで実行された場合とかに、アプリの使用方法を表示する場合があると思う。あるいは"-h"とか"--help"とかのオプションを定義しておいて、実行時にこれらが設定されていた場合に、使用方法を表示するなんて場合もそう。

このような場合に定型の表示をしてくれるユーティリティにHelpFormatterがいる。

HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("MyCmd.jar [options] <foobar>", options);

上記のようにしてOptionsインスタンスを引数にして呼び出すことで、Optionオブジェクト生成時に設定した情報をもとにして使用方法のテキストを自動整形して標準出力に出力してくれる。これは便利・・・。

*1:今回使用したCLIとは異なるアプローチをしているライブラリとしてJArgpがある。このライブラリの使用方法については「Java プログラミングのダイナミックス: 第 3 回 実用的なリフレクション」が参考になる。

*2:CLI=Command Line Interfaceってことらしい。

*3:ほかにも引数を省略した簡略版メソッドが提供されている。

*4:けれどもこのページで登場するDefaultParserなるクラスはバージョン1.2のjar内には存在しない・・・。