コマンドの多くは、引数の他に -la, --version のようなオプションを受け取って柔軟に挙動を変化させます。 そのような便利なコマンドを作るには、main 関数が受け取った argv の解析が必要です。 これには C 標準ライブラリに含まれる getopt 系関数がとても便利なので紹介します。

ここで扱うオプションとは

ところでオプションとは、ここでは引数のうち - で始まり、かつ - 単独や -- 単独でない文字列を指します。 例えばプログラムの引数が次のようだったら、

-a -b - -C D -- e

-a, -b, -C はオプションになります。

オプションを解析するには getopt

getopt(3) はコマンドライン引数 argv を解析して、オプションを見つけます。 親切にもマニュアルにプログラム例が載っていますので適宜省略しつつ引用させていただきます。

#include <unistd.h>

int
main(int argc, char *argv[])
{
    int opt;
    while ((opt = getopt(argc, argv, "nt:")) != -1) {
        switch (opt) {
        case 'n':
            // n のときの仕事
            break;
        case 't':
            // t のときの仕事
            break;
        default:
            // 非対応のオプションが来たときの仕事(大抵はエラー出して終了)
            break;
        }
    }

    // オプション以外の引数を扱う
}
// 出典を基に一部省略及び改変
// 出典: getopt(3) EXAMPLES https://www.man7.org/linux/man-pages/man3/getopt.3.html#EXAMPLES

getopt は argv の中のオプションを見つけて、その文字を返し、発見し尽くしたら -1 を返します。 例えば -n オプションに対しては 'n' が返るという調子なので、それで条件分岐をします。 argv を最後まで解析するために、多くの場合 getopt を繰り返し実行します。

getopt は argc, argv の他に getopt が受け付けるオプションの定義を受け取ります。 各英数字がオプションを表していて、コロン : は直前のオプションが引数を取るか取らないかを表します。 今回の例 "nt:" でいうと、

  • n – -n を受け付ける。引数は取らない
  • t – -t を受け付ける。引数が必須である

となるので、このプログラムは -n -t some_value というような引数をオプションとして受け付けます。 -t の引数 some_value は optarg というグローバル変数で取得できます。

逆に、この定義に無いようなオプションを与えると getopt はエラーを表示して、'?' を返します。 エラーは表示されるだけなので、プログラム側で無視すれば処理を続行できます。

getopt のループが終わったら、普通はオプション以外の引数について処理を行います。 このときグローバル変数 optind が役立ちます。 optind は argv の中でオプションでない最初の引数の位置を記憶しています。 なので argv を第 1 要素からではなく、第 optind 要素から参照すればよいわけです。

例えばマニュアルのプログラムの次の部分では、オプションではない最初の引数を表示しています。

    printf("name argument = %s\n", argv[optind]);
    // 出典: getopt(3) EXAMPLES https://www.man7.org/linux/man-pages/man3/getopt.3.html#EXAMPLES

コマンドライン引数のオプションと引数の順序は、通常気にしなくて良いです。つまり、-a b -c -d e というコマンドライン引数を与えたときは、getopt が argv の並べ替えを行って -a -c -d b e と同じようにしてくれるため、第 optind 要素以降はすべて非オプション引数として扱えます。ただし、POSIXLY_CORRECT という環境変数が設定されているとこの並べ替えは実行されないので、その場合は順序に注意する必要があります。

長いオプションも処理するには getopt_long

--version などの長いオプションを扱いたいときは getopt_long(3) を使います。 ヘッダーが変わって getopt.h が必要ですが、やることはだいたい一緒です。 getopt を機能的にまるっと包含している(getopt ⊂ getopt_long)ので、簡単に置き換えができます。

#include <getopt.h>

int
main(int argc, char *argv[])
{
    const struct option long_options[] = {
        {"n", no_argument, 0, 'n'},
        {"tee", required_argument, 0, 't'},
        {"key", required_argument, 0, 0},
        {0, 0, 0, 0}
    };

    while (1) {
        int opt, long_optind;
        opt = getopt_long(argc, argv, "n", long_options, &long_optind);
        if (opt == -1)
            break;

        switch (opt) {
        case 0: // 長いオプションをみつけた
            if (!strcmp(long_options[long_optind].name, "key")) {
                // --key のときの仕事
                // --key の引数は optarg グローバル変数で取得できる
            }
            break;
        case 'n':
            // ...
        case 't':
            // ...
        default:
            // ...
        }
    }

    // メインの仕事
}
// 出典を基に一部省略および改変。
// 出典: getopt(3) EXAMPLES https://www.man7.org/linux/man-pages/man3/getopt.3.html#EXAMPLES

違いは、

  • struct option 型の配列で受け取るオプションの定義を書く
  • getopt_long が見つけた長いオプションが定義の何番目に当たるかを示す long_optind 変数を用意する

という点です。なんとなく、雰囲気が伝わると思います。

余談

実装すれば絵文字オプションも使える。

$ ./cat.elf --😈
I am the Demon King😈
$

発想の源(Node.js だけど): https://www.npmjs.com/package/ganache#detached-instances