コマンドライン引数の処理

このエントリーをはてなブックマークに追加

argc、argvを自前で処理するのは想像しただけで重い気持ちになりますね。
このやっかいなコマンドラインを処理する関数がgetopt(2)です。ここではgetoptについて解説します。

getopt(3)

Unix OSを前提として汎用的に処理するならばgetopt(3)が唯一の関数です。
getopt(3)は'-v'といった'-'とオプション文字(文字列ではない!)をコマンドライン引数としてみなして処理します。つまり、'--version'といったものはオプションとして認識しません。
OSを気にせず使用できますが制限が多いのが欠点です。

まずはプロトタイプです。

#include <unistd.h>

int getopt(int argc, char * const argv[], const char *optstring);

//これはerrnoと同様にincludeした時点で使用可能です
extern char *optarg;
extern int optind, opterr, optopt;

ではサンプルを見ていきます。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	int opt;

	while ((opt = getopt(argc, argv, "hvn:")) != -1) {
		switch (opt) {
			case 'n':
				printf("option n:%s\n", optarg);
				break;
			case 'v':
				printf("version:0.0.1\n");
				break;
			case 'h':
				printf("help message...\n");
				break;
			default: /* '?' */
				printf("help message...\n");
				return -1;
		}
	}

	return 0;
}


解説

オプションは3つあります。
-h、-vオプション。これはよくあるhelpとversionを表示します。オプションの引数は存在しません。

$ ./getopt -h
help message ...
$ ./getopt -v
version:0.0.1

これに対し-nオプションは渡された引数をそのまま表示します。-n 1ならば1を表示します。
$ ./getopt -n 1
option n:a
$ ./getopt -n a
option n:a


引数の有り無しはgetoptの第3引数で決めれます。'n:'のようにコロン(:)があればオプションに引数が必要となります。
// この場合だと
// hとvは引数が必要ない
// nとcには引数が必要
getopt(argc, argv, "hn:c:v")


getopt_long(3)

getoptでは'--version'といった長いオプションを使用できません。
これに対応するのがgetopt_long(3)です。getopt_long_only(3)という少し異なるバージョンも存在しますがこれは'-version'というハイフン1つにも対応するだけです。
ただし、これらはGNU拡張ですので移植を考える場合には使用できません。
まずはプロトタイプです。

//unistd.hではなくgetopt.hなので注意!
#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);

さてサンプルです。あまり良いオプション例を思いつかなかったのでjamnから引用してみます。
#include <stdio.h>     /* for printf */
#include <stdlib.h>    /* for exit */
#include <getopt.h>

int
main(int argc, char **argv) {
    int c;
    int digit_optind = 0;

    while (1) {
        int this_option_optind = optind ? optind : 1;
        int option_index = 0;
        static struct option long_options[] = {
            {"add",     required_argument, 0,  0 },
            {"append",  no_argument,       0,  0 },
            {"delete",  required_argument, 0,  0 },
            {"verbose", no_argument,       0,  0 },
            {"create",  required_argument, 0, 'c'},
            {"file",    required_argument, 0,  0 },
            {0,         0,                 0,  0 }
        };

        c = getopt_long(argc, argv, "abc:d:012",
                 long_options, &option_index);
        if (c == -1)
            break;

        switch (c) {
        case 0:
            printf("option %s", long_options[option_index].name);
            if (optarg)
                printf(" with arg %s", optarg);
            printf("\n");
            break;

        case '0':
        case '1':
        case '2':
            if (digit_optind != 0 && digit_optind != this_option_optind)
              printf("digits occur in two different argv-elements.\n");
            digit_optind = this_option_optind;
            printf("option %c\n", c);
            break;

        case 'a':
            printf("option a\n");
            break;

        case 'b':
            printf("option b\n");
            break;

        case 'c':
            printf("option c with value '%s'\n", optarg);
            break;

        case 'd':
            printf("option d with value '%s'\n", optarg);
            break;

        case '?':
            break;

        default:
            printf("?? getopt returned character code 0%o ??\n", c);
        }
    }

    if (optind < argc) {
               printf("non-option ARGV-elements: ");
               while (optind < argc)
                   printf("%s ", argv[optind++]);
               printf("\n");
           }

           exit(EXIT_SUCCESS);
       }

動作を見てみましょう。
$ ./getopt_lng --add a
option add with a
$ ./getopt_lng --append
non-option ARGV-elements: a
$ ./getopt_lng --delete a
option add with a
$ ./getopt_lng --verbose
option verbose
$ ./getopt_lng --create a
option c whith value 'a'
$ ./getopt_lng -c a
option c whith value 'a'
$ ./getopt_lng --file a
option file whith value 'a'


解説

オプションの動作を決めるのは、この部分です。

        static struct option long_options[] = {
            {"add",     required_argument, 0,  0 },
            {"append",  no_argument,       0,  0 },
            {"delete",  required_argument, 0,  0 },
            {"verbose", no_argument,       0,  0 },
            {"create",  required_argument, 0, 'c'},
            {"file",    required_argument, 0,  0 },
            {0,         0,                 0,  0 }
        };

構造体の宣言は次の通り。
struct option {
    const char *name;
    int         has_arg;
    int        *flag;
    int         val;
};

nameは長いオプションの名前です。
has_arg。オプションの引数についてです。no_argument (または 0) なら、オプションは引き数をとらない。 required_argument (または 1) なら、オプションは引き数を必要とする。 optional_argument (または 2) なら、オプションは引き数をとっても とらなくても良い。
flag。長いオプションに対する結果の返し方を指定する。flag が NULL なら getopt_long() は val を返す (例えば呼び出し元のプログラムは、 val に等価なオプション文字を代入することができる)。 NULL 以外の場合には、 getopt_long() は 0 を返す。 このときオプションが見つかると flag がポイントする変数に val が代入される。見つからないとこの変数は変更されない
。 val。返り値、または flag がポイントする変数へロードされる値。

ちょっと慣れが必要ですが理解さえすれば非常に便利です。ただしLinux環境下だと...。
移植性が必要で長いオプションが必要な場合はどうするかというと、自前で実装するのが一般的なようです。これにはポインタと文字列処理が必要で面倒で長いな処理になりがちなのですが決定的な方法がないようですね。仕方がありません。


まとめ

実用では必要な処理ですが自前で要するのは大変というオプション処理ですが、こういった部分にも配慮されているのが素晴らしいですね。
無駄に頑張っていた人はぜひ使ってみてください。