C言語で自力でオプション解析をする方法を解説【getoptは使いません】
目次
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.h
はprintf()
など。
stdbool.h
はbool
です。
string.h
はstrcmp()
など。
stdlib.h
はexit()
などを使うために必要です。
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
から始まります。
ですのでargc
が2
より下というのはプログラムのパスしか入っていない、つまりコマンドの引数が空、という状態です。
for (int i = 1; i < argc; i++) { const char *arg = argv[i]; ... }
↑コマンドライン引数をfor文で回して解析します。
argv
の0
番目にはプログラムのパスが入っていますがこれは解析する必要がないのでカウント変数i
は1
から始めます。
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_arg
をprintf()
で出力します。
以上です。
おわりに
今回は古のオプション解析を解説しました。
普通はgetopt
やgetopt_long
を使うのが良いと言われていますが、こういった基礎的な解析を押さえておくと自分の作れるプログラムの幅が広がります。
またこれらの関数を使えない場合も有効です。
(^ _ ^) | オプションを解析! |
(・ v ・) | ヘルプ! |