ユーニックス総合研究所

  • home
  • archives
  • c-parse-opts

C言語で自力でオプション解析をする方法を解説【getoptは使いません】

  • 作成日: 2023-01-02
  • 更新日: 2023-12-24
  • カテゴリ: C言語

C言語のコマンドのオプション解析

C言語のコマンドプログラムのオプション解析ですが、これは普通は

  • getopt
  • getopt_long

などの関数を使って解析します。
しかし古(いにしえ)の流儀ではこれらの関数を使わずに自力でオプション解析することも行われています。

今回はこの古の流儀である自力のオプション解析を解説します。

今回作るプログラムの概要

今回はサンプルプログラムとしてシンプルなコマンドを作ります。
コマンドを実行すると↓のような結果になります。

$ ./a.out  
Usage: a.out [options]  

The options are:  

   -h, --help          Show usage.  
   -s, --show [arg]    Show arg.  

このコマンドはオプションを2種類合わせて4つ持っているコマンドです。
オプション-h--helpはコマンドの使い方を表示するオプションです。
このオプションを指定するとコマンドはコマンドの使い方を表示します。

$ ./a.out --help  
Usage: a.out [options]  

The options are:  

   -h, --help          Show usage.  
   -s, --show [arg]    Show arg.  

オプション-s--showはオプションの引数を画面に出力するオプションです。
これは↓のように使います。

$ ./a.out --show Hello  
Show: Hello  

オプションの種類

オプションには大きく分けて↓の2つがあります。

  • ショートオプション
  • ロングオプション

ショートオプション-h-sなどハイフンが1つだけのオプションです。
これはロングオプションの短縮形としてよく使われます。

ロングオプションは--help--showなどハイフンが2つのオプションです。
複数の単語を使う時は--show-argなどとハイフンで区切って設定します。

ちなみにWindowsのコマンドですがオプションは/Hとか/Sとかスラッシュで指定することが多いです。
この辺はプラットフォームの設計思想によって仕様が違うことがあるので注意が必要です。

ソースコードの解説

ソースコードを解説します。
まずは必要なヘッダーからです。

インクルードするヘッダー

インクルードするヘッダーは↓になります。

#include <stdio.h>  
#include <stdbool.h>  
#include <string.h>  
#include <stdlib.h>  

stdio.hprintf()など。
stdbool.hboolです。
string.hstrcmp()など。
stdlib.hexit()などを使うために必要です。

Options構造体

オプションを解析してその解析結果を保存する構造体が必要になります。
今回はOptionsという構造体を作ってこの構造体のメンバに解析結果を保存していくことにします。

struct Options {  
    bool is_help;  
    bool is_show;  // 引数を取るオプション  
    const char *show_arg;  
};  

is_help-h--helpオプションが指定されたときにtrueになるフラグです。
is_show-s--showオプションが指定されたときにtrueになります。
show_argには--showオプションの引数が保存されます。

usage関数

プログラムの使い方を表示して終了するusage関数を作っておきます。

void usage(void) {  
    printf("Usage: a.out [options]\n"  
"\n"  
"The options are:\n"  
"\n"  
"   -h, --help          Show usage.\n"  
"   -s, --show [arg]    Show arg.\n"  
"\n");  
    exit(0);  
}  

C言語のダブルクオートで囲んでいる文字列定数は複数並べると自動で結合されます。
ですので↑のように複数行にわたって文字列定数を並べることができます。

exit()は引数の終了ステータスでプログラムを終了する関数です。
終了ステータスが0の場合は正常終了、0以外の場合は異常終了になります。

オプションの解析

では肝心かなめのオプション解析です。

int main(int argc, char *argv[]) {  
    struct Options opts = {0};  

    if (argc < 2) {  
        opts.is_help = true;  
    } else {  
        for (int i = 1; i < argc; i++) {  
            const char *arg = argv[i];  
            if (strcmp(arg, "--help") == 0 ||  
                strcmp(arg, "-h") == 0) {  
                opts.is_help = true;  
            } else if (strcmp(arg, "--show") == 0 ||  
                       strcmp(arg, "-s") == 0) {  
                i++;  
                arg = argv[i];  
                if (arg == NULL || arg[0] == '-') {  
                    fprintf(stderr,  
                        "invalid show option. need the argument.\n");  
                    exit(1);  
                }  
                opts.is_show = true;  
                opts.show_arg = arg;  
            } else {  
                fprintf(stderr, "invalid options %s\n", arg);  
                exit(1);  
            }  
        }  
    }  

    if (opts.is_help) {  
        usage();  
    } else if (opts.is_show) {  
        printf("Show: %s\n", opts.show_arg);  
    }  

    return 0;  
}  

まずOptionsの変数を初期化します。

    struct Options opts = {0};  

opts = {0};とやると構造体のメンバを0クリアできます。
boolなどは0クリアするとfalseに、ポインタはNULLになります。

    if (argc < 2) {  
        opts.is_help = true;  
    } else ...  

コマンドライン引数argvの長さを表すargcの値が2より下だったらopts.is_helpフラグを立てます。
argcの値が2より下というのはつまりコマンドの引数が空という状態です。
argvには0番目の要素にプログラムのパスが入るのでargcの値は1から始まります。
ですのでargc2より下というのはプログラムのパスしか入っていない、つまりコマンドの引数が空、という状態です。

        for (int i = 1; i < argc; i++) {  
            const char *arg = argv[i];  
            ...  
        }  

↑コマンドライン引数をfor文で回して解析します。
argv0番目にはプログラムのパスが入っていますがこれは解析する必要がないのでカウント変数i1から始めます。

            if (strcmp(arg, "--help") == 0 ||  
                strcmp(arg, "-h") == 0) {  
                opts.is_help = true;  
            } ...  

arg--helpまたは-hだったらopts.is_helpフラグを立てます。
これによってコマンドでこれらのオプションが指定されたらフラグが立つようになります。

            ... {  
                ...  
            } else if (strcmp(arg, "--show") == 0 ||  
                       strcmp(arg, "-s") == 0) {  
                i++;  
                arg = argv[i];  
                if (arg == NULL || arg[0] == '-') {  
                    fprintf(stderr,  
                        "invalid show option. need the argument.\n");  
                    exit(1);  
                }  
                opts.is_show = true;  
                opts.show_arg = arg;  
            } ...  

arg--showまたは-sだったらopts.is_showフラグを立てます。
--showオプションは引数を取るのでそのための処理も行っています。
まずカウント変数iをインクリメントして1つ先のコマンドライン引数を取得します。
argvは終端がNULLになっています。
ですのでここで取得したargがNULL、またはオプションだったら--showの引数が存在しないということになります。
その場合はエラーを出力してexit(1)しておきます。

取得したオプションの引数はopts.show_argに保存しておきます。
argvはプログラムが終了するまで生きますのでポインタ変数をそのまま入れておいてもOKです。
もっとも値を加工したい場合は文字配列にコピーしておいた方が良いでしょう。文字列定数は加工できません。

            ... {  
                ...   
            } else {  
                fprintf(stderr, "invalid options %s\n", arg);  
                exit(1);  
            }  

↑対応していないオプションだった場合はエラーを出力しておきます。

    if (opts.is_help) {  
        usage();  
    } else if (opts.is_show) {  
        printf("Show: %s\n", opts.show_arg);  
    }  

↑解析したオプションを使ってプログラムのふるまいを変更しています。
opts.is_helpが立っていたらusage()を表示。
opts.is_showが立っていたらopts.show_argprintf()で出力します。

以上です。

おわりに

今回は古のオプション解析を解説しました。
普通はgetoptgetopt_longを使うのが良いと言われていますが、こういった基礎的な解析を押さえておくと自分の作れるプログラムの幅が広がります。
またこれらの関数を使えない場合も有効です。

🦝 < オプションを解析!

🐭 < ヘルプ!