コマンドラインオプションの処理

作成:

コマンドラインオプションの処理を行おうと思うと、 main関数の引数を利用すれば良く、 あとは自分で決めたルールに従いパースすればよい。

ただ、各プログラムごとに必要となる引数やその意味などは異なって当たり前だが、 引数の与え方、引数のフォーマット、という観点では、 ある程度標準的な方法をとった方がプログラマ・ユーザ双方にとってメリットがある。 例えばlsというコマンドで、"."から始まる隠しファイルも含めて表示させるオプションは-a、 パーミッション情報など詳細情報を表示させるオプションは -l である。 両方のオプションを同時に指定する場合は、 ls -a -lls -l -a と、どちらを先に指定してもよいし、 ls -la といった形でまとめて指定しても同じ意味になる。 また、コンパイルで利用するgccでは、gcc -o hoge hoge.cといった形で、 -oオプションの後に指定される引数が出力ファイル名となる、 といった引数を持つオプションという概念があったりする。 こういったルールは既存の多くのコマンドラインツールで共通に採用されているため、 それに習って同じように使えるようにするのが良いはずだ。

そこで、そういった引数の扱い方をフォローしてくれる関数が用意されている。 それが getopt() 関数だ。

getopt関数の利用

man を使って調べると以下の様な定義が出てくる。

#include <unistd.h>
int getopt(int argc, char * const argv[], const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;

unistd.h(UNIX標準機能のヘッダ)を include して使用する。 Linuxの場合、このヘッダ内部で include されている、getopt.h に各定義が記述されている場合が多いようだ。

引数の argcargv は、main関数の引数をそのまま渡せば良い。 最後の optstring は、この関数で処理するオプションを列挙した文字列である。 詳細は以下のサンプルプログラムを動かしながら説明しよう。

#include <stdio.h>
#include <unistd.h>

int main(int argc, char*argv[]) {
  int i;
  int aopt = 0;
  int bopt = 0;
  int copt = 0;
  int dopt = 0;
  char *cparam = NULL;
  char *dparam = NULL;
  int opt;
  while ((opt = getopt(argc, argv, "abc:d::")) != -1) {
    switch (opt) {
      case 'a':
        aopt = 1;
        break;
      case 'b':
        bopt = 1;
        break;
      case 'c':
        copt = 1;
        cparam = optarg;
        break;
      case 'd':
        dopt = 1;
        dparam = optarg;
        break;
      default:
        printf("error! \'%c\' \'%c\'\n", opt, optopt);
        return 1;
    }
  }
  printf("a = %d\n", aopt);
  printf("b = %d\n", bopt);
  printf("c = %d, %s\n", copt, cparam);
  printf("d = %d, %s\n", dopt, dparam);
  for (i = optind; i < argc; i++) {
    printf("arg = %s\n", argv[i]);
  }
  return 0;
}

cparam/dparamのprintfでの文字列出力で、値がNULLだった場合のケアを入れていない。 実際には当初は入れていたが、私の使用している環境では、 引数の値がNULLの場合"(null)"と出力されることを確認し、余計なコードを省くため削除した。 ただ、環境によってはハングする可能性があるのでご注意を。 その場合、 cparam == NULL ? "(null)" : cparam などと置換を行うことで、 ほぼ同等の動作になると思う。

optstring には、 "abc:d::" を指定している。 abcdの4種類のオプションを処理する、という意味だ。 : (コロン)は、その直前のオプションが引数を持つことを示している。 また :: と2つ続けてコロンがあるものは、 その直前のオプションの引数がオプション (……ややこしいが、引数を保つ場合も持たない場合もどちらもある)という意味になる。 総じて、abcdという4つのオプションがあり、cは引数必須、dは引数オプション。という意味になる。

まずは実行してみよう

$ gcc -o getopt getopt.c
$ ./getopt -a -b
a = 1
b = 1
c = 0, (null)
d = 0, (null)

$ ./getopt -ab
a = 1
b = 1
c = 0, (null)
d = 0, (null)

-a -b や、 -ab と、どちらで記述しても同じ意味になっている。 また、特に必要はないと思うが仮に -aa といった形で同じオプションを複数指定した場合、 getopt() は指定された回数同じオプションを返す。 順序を逆に指定すると当然取り出される順序は異なるが、最終的に立てられるフラグは変わらない。

getoptは、コールするごとにコマンドライン引数を処理し、 見つけたオプション文字(char)の値を戻り値(int)として返す。 処理するオプションがなくなった場合は、 -1 が返る。 エラーとして負の値を返すために、戻り値がcharではなく、intになっている。 また、 optstring で指定していない引数が見つかった場合は、 エラーメッセージを標準エラー出力に出力し、戻り値として '?' が返る。

では、optstring にない引数をつけて実行してみよう。

$ ./getopt -e
./getopt: invalid option -- 'e'
error! '?' 'e'

./arg: invalid option -- 'e' はコード上に記述していないメッセージ、これはgetoptが出力をしている。 エラーメッセージを表示した後、 '?' が戻り値として返っている。 そして、その時のオプション文字は戻り値で返されず、 optopt に格納されている。

ここでは想定外の文字が来た時、エラーとして処理を停止しているが、 想定外のオプションが指定された場合は、 それを無視して実行を続ける、という実装でも良いだろう。 そこは作ろうとしているプログラムの仕様に合わせてご自由に。

すでに、 optopt の値について言及してしまったが、 getopt()は関数単体で完結している処理ではなく、グローバルスコープの変数を使っている。 それが定義のところに書かれている。 char *optarg; int optind, opterr, optopt; である。 この内、 opterr に 0 を代入しておくと、 getopt 自体がエラーメッセージを出力しなくなる。 前述したように想定外の引数が指定されても処理を続ける場合や、 独自のエラーメッセージを表示したい場合などでは、 opterr = 0; を処理を始める前に書いておくと良い。 また、不明な引数の場合、 getopt() の戻り値は、 '?' になるが、 見つけた不明なオプションの値は optopt に代入されるので、 どのように入力されたかが知りたい場合はこれを利用する。

次に、引数を持つオプションを試してみる。

$ ./getopt -c arg
a = 0
b = 0
c = 1, arg
d = 0, (null)

$ ./getopt -carg
a = 0
b = 0
c = 1, arg
d = 0, (null)

引数がある場合は、 optarg へ、そこを指すポインタが代入されるので、そこから取得する。 このポインタの参照先は処理中に変化するわけではないので、そのままポインタを保存しても問題ない。 また、引数は、 -c arg とスペースを開けて書いても、 -carg と続けて書いても同じように処理される。 また、その都合上、利用者は引数を個別に書かずまとめて書く場合、引数を取るオプションは最後に指定する必要がある。 なぜなら、引数を持つオプションに続く文字列は、 次の空白までがそのオプションの引数であり、オプションとしては処理されないからだ。 例えば、上記の -carg の場合、cの次にaが来ているが、これは -a を指定したことにはならない。

次に、引数を取るオプションに引数を書かなかった場合。

$ ./getopt -c
./arg: option requires an argument -- 'c'
error! '?' 'c'

$ ./getopt -d
a = 0
b = 0
c = 0, (null)
d = 1, (null)

と、このように、コロン1つの場合、引数は必須なので、指定したオプションとは違う扱いになる。 一方、コロン2つの場合は、引数はオプションなので、引数なし(NULL)で処理される。

最後に、オプション以外の引数があった場合

$ ./getopt aaaa -c a
a = 0
b = 0
c = 1, a
d = 0, (null)
arg = aaaa

$ ./getopt -c a bbbb
a = 0
b = 0
c = 1, a
d = 0, (null)
arg = bbbb

$ ./getopt aaaa -c a bbbb
a = 0
b = 0
c = 1, a
d = 0, (null)
arg = aaaa
arg = bbbb

getoptはargvの並び順を変更し、オプション以外の引数は、末尾にまとめるように動作する。 また、グローバルスコープの変数 optind は、 getopt が次に処理する引数のindexを示している。 オプションがすべて処理し終わった時点での optind は、オプション以外の引数の先頭を示している。 (オプション以外の引数がない場合は当然末尾を指すことになる) そこで、サンプルプログラムでは残りの引数を表示させている。

gcc の引数もそうだが、よくあるのが必須の引数として処理するファイル名を指定し、 それ以外をオプションとする場合。残った引数をこのようにして取り出す。 オプションとそれ以外の引数の位置は前でも後ろでも同様に処理できる。 必須の引数をそのようにしなければならいわけではなく、オプションの中に必須がものもあってよくて、 解析の結果そのオプションが指定されていなければエラーとすれば良い。

getopt() を使ったコマンドラインオプションの解析に関する説明を行ったが、 これを利用すれば、既存のコマンドラインツール群と同じようなインターフェースのツールを自作することができる。 自分で作って自分で使うだけならいくらでも自由な仕様でいいし、引数の解析方法も自由にやればいいと思う。 だが、自分以外の人、もしくは数ヶ月、数年後の自分が使えるものを作ろうと思っているならば、 この方法をおすすめする。