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

作成:

getopt() を使えばPOSIX標準のコマンドラインオプションのルールを実装することができるのだが、 引数の扱いにはGNUの拡張として、ハイフン2つ+複数文字(単語)の構成のコマンドラインオプションが使われることがある。 例えば、先に出したlsの例だと、 -a と同等の意味を持つオプションとして --all というものが定義されている。 実際、数個のオプションならハイフン1つ+一文字のシンプルなオプションで十分なのだが、 コマンドラインオプションの数が増えていくと一文字のバリエーションでは表現が難しくなり、使いにくいものになってしまう。 そこで、ハイフン2つ+単語の構成のコマンドラインオプションを扱うための関数が用意されている。 それが、 getopt_long() getopt_long_only() である。 通常のオプション(「短い」オプション)に対して「長い」オプションを処理するための関数だ。

getopt_long関数の利用

manで調べると、定義は以下のようになっている。

#include <getopt.h>

int getopt_long(int argc, char * const argv[], const char *optstring,
                const struct option *longopts, int *longindex);
int getopt_long_only(int argc, char * const argv[], const char *optstring,
                     const struct option *longopts, int *longindex);

ここで、はじめの3つの引数については、getopt() と全く同じで、 意味としても全くおなじになる。 長いオプションだけしか扱えないのではなく、両方が扱えるようになっている。 そして、4つ目以降の引数が肝心の長いオプションを処理するために必要な引数である。 longopts に長いオプションの定義を入れる。 指定するのは一つ一つのオプションを定義した構造体の配列となる。 また、この配列は配列長を渡す代わりに0終端を行う。 続く引数の longindex は、NULLを指定しても良いが、 有効な変数へのポインタを指定すると、 longopts で指定した何番目のオプションに合致したかというインデックス値が格納される。

ここで、長いオプションを定義する struct option は以下の様な構造体になっている。

struct option {
  const char *name;
  int has_arg;
  int *flag;
  int val;
};

name は、マッチさせる長いオプション、 例えば --add というオプションを定義したい場合は "add" と指定する。
has_arg はこのオプションが引数を持つかどうかの指定である。 getopt() と同様に、なし、必須、あってもなくてもよい、が定義されている。 指定する値は getopt.h で、以下のように定義されている。 これら定義値を利用して指定する。

# define no_argument            0
# define required_argument      1
# define optional_argument      2

flag は、判定結果の格納先を指定する。 ここに格納先変数のポインタを指定すると、関数の戻り値は0になり、 代わりに指定された変数へ値が格納される。 ただし、 getopt() と同様に使用することを考えると、NULLを指定し、 関数の戻り値として、 getopt() と同じ戻り値が返るようにしたほうが良いと思う。
val は、判定結果として返す値を指定する。

例によって、言葉で説明しても難しいので、実コードと実動作を通して説明していく。

#include <stdio.h>
#include <getopt.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;
  struct option longopts[] = {
      { "add",    no_argument,       NULL, 'a' },
      { "break",  no_argument,       NULL, 'b' },
      { "clear",  required_argument, NULL, 'c' },
      { "delete", optional_argument, NULL, 'd' },
      { 0,        0,                 0,     0  },
  };
  int opt;
  int longindex;
  while ((opt = getopt_long(argc, argv, "abc:d::", longopts, &longindex)) != -1) {
    printf("%d %s\n", longindex, longopts[longindex].name);
    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;
}

ほぼ、 getopt() で利用したサンプルコードと同じ引数を受け付けるものとした、 また、 getopt() で利用した、各引数に対して、以下の様な対応になる。 単語に特に意味なはく、頭文字が合うように適当な単語を選んだだけなのでそこは目をつむってほしい。

-a --add 引数なし
-b --break 引数なし
-c --clear 引数必須
-d --delete 引数オプション

また、短いオプションと長いオプションをまとめて扱えるように、 長いオプションを検出した時に、対応する短いオプションの文字が戻り値として返るように longopts を定義している。 ここで、 longopts の最後に、 全項目が 0 に設定された要素があるのに注目。 この項目で配列の末尾を表現している。

では、実際に実行してみよう。

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

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

このように、長いオプションが短いオプションと同様に検出できる。 また、2つ目の実行結果のように、長いオプションは必ずしも全てを入力しきる必要はなく、 他のオプションと区別できるだけの文字数が入力できていればよい。 この場合、bから始まる他のオプションが無いため、 --b と、一文字で --break と同じ結果が得られる。

短いオプションと混在して指定することもできる。

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

この場合、 -b は、前述の --b のように長いオプションを短縮して指定したものではなく、 optstring で指定した、 getopt() と同じ処理によって判別された、短いオプションである。 また、longindex を使用した出力を見てもらえば分かると思うが、 longindex の値が反映されるのは、 longopts によって指定されたオプションを検出した場合だけである。 ここからも -b の検出は、 longopts ではなく、 optstring で指定した引数として検出されていると分かる。 longindex を使って引数を判定するときは気をつけよう。

次に引数であるが、 引数必須のオプションの引数を省略した場合の動作は getopt() と同じ。

$ ./getopt_long --clear
./getopt_long: option '--clear' requires an argument
0 add
error! '?' 'c'

長いオプションの引数は --option=argument 、もしくは --option argument のどちらかが使用できる。

$ ./getopt_long --clear=test
2 clear
a = 0
b = 0
c = 1, test
d = 0, (null)

$ ./getopt_long --clear test
2 clear
a = 0
b = 0
c = 1, test
d = 0, (null)

どちらでも同じ結果が得られる。

最後に、このサンプルの getopt_longgetopt_long_only とした場合どうなるか?

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

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

このように、 getopt_long_only を使うと、 --option だけでなく、 -option というハイフン一つのオプションもまず、長いオプションとして処理をしようとする。 そのため、 -add と書いても、addオプションとして判定される。 一方、 getopt_long では、 -add が、短いオプションとして処理されるため、 aオプション、続いてdオプション、dオプションの引数d、と解釈されてしまう。

では、 getopt_long_onlyoptstring 引数は無駄か、というと 必ずしもそうではなく、長いオプションとして判断できなかった引数が、短い引数として判定される。

たかが引数、されど引数ということで、3回に渡る説明で、 ちょっと長くなってしまった感があるが、なんとなくどのように作ればよいか理解いただけただろうか? きちんとしたコマンドラインツールを作ろうと思ったら必須と言ってもいい技法であるし、 そうでなくても、世の中に出回っているツールと同じインターフェースを実現できるということは、 プログラミング入門者にとってモチベーションの上がることではないだろうか? ということで、たとえ学校の課題でプログラミングをしているだけ、という人も、 ちょっとこういう細工を入れてみるのも楽しいかもしれない。 ぜひ挑戦してみてもらいたい。