C言語でコマンドライン引数を取り扱う方法
C言語でコマンドラインオプションを解釈するには、ざっくりと2つの方法がある。
・特に関数を使わずにコマンドラインオプションを解釈する方法
・getopt関数などを使ってコマンドラインオプションを解釈する方法
別にどっちでも良い。
オプションが1個か2個とかなら前者、いっぱいなら後者、ぐらいの差。
あと後者のほうがソースコードレビューの時ちょっとかっこいい。
特に関数を使わずにコマンドラインオプションを解釈する方法
int main(int argc,char* argv[]) のargcとargvを直接操作してコマンドラインオプションを解釈していく。
argcはコマンドラインオプションの数なのだが、ちょっとわかりにくいのがコマンドそのものも含んでいる点。
例えば以下のようにコマンドを実行した場合。
$ ./sample1 aaa bbb ccc
argcの値は3ではなく、4になる。
以下にコード例を示す。
[sample1.c]
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("argc=%d\n", argc);
return 0;
}
実行。
$ gcc -o sample1 sample1.c
$ ./sample1 aaa bbb ccc
argc=4
argvはコマンドラインオプションの各文字列の先頭アドレスとなる。
$ ./sample2 aaa bbb ccc
argv[0] : "./sample2"文字列の先頭アドレス
argv[1] : "aaa"文字列の先頭アドレス
argv[2] : "bbb"文字列の先頭アドレス
argv[3] : "ccc"文字列の先頭アドレス
んで、サンプルコード。
[sample2.c]
#include <stdio.h>
int main(int argc, char *argv[])
{
if (argc == 4) {
printf("argv[0] : %s\n", argv[0]);
printf("argv[1] : %s\n", argv[1]);
printf("argv[2] : %s\n", argv[2]);
printf("argv[3] : %s\n", argv[3]);
}
return 0;
}
からの実行。
$ gcc -o sample2 sample2.c
$ ./sample2 aaa bbb ccc
argv[0] : ./sample2
argv[1] : aaa
argv[2] : bbb
argv[3] : ccc
こちらもちょっとわかりにくいのが、一番最初がコマンド名な点。
いらないと思っていたが、busyboxのような一つのプログラムなのに複数のコマンドの振る舞いをするコマンドなどで用いられている。(lnでlsとかcatとかいう名前でリンクを張ることによりbusybox一つでたくさんの挙動を振り分けて実行できている)
以上より、argcで個数が、argvで文字列がわかるので、それぞれを仕様にあわせて頑張ってゴリゴリ実装するだけ。
でも、それだとオプションの数が増えたり、指定する順番を変えたい、とか、オプションに値を持たせたいとかいろいろ考えるだけで大変そう。
そういったのを楽にしてくれるのが、getopt関数。
getopt関数を使ってコマンドラインオプションを解釈する
getopt関数は引数にmainから渡されてきたargcとargvを、そしてオプション文字列を渡して実行する。
すると、コマンドラインオプションを「1つづつ解釈」して、戻り値に値を返してくる。
ひとつづつ、ということかわかるように、ループ処理して全部のコマンドラインオプションを処理する必要がある。
#include <unistd.h>
int getopt(int argc, char * const argv[], const char *optstring);
引数 | 説明 |
---|---|
argc | mainから渡されたargcを放り込めばよい |
argv | mainから渡されたargvを放り込めばよい |
optstring | オプション文字列。-aというオプションを使いたいなら"a"を、-aと-bを使いたいなら"ab"といった感じで設定する 値を持つオプションにしたい場合は"a:"といったようにコロンを付ける。これで-a 100といったオプション指定ができるようになる ::とコロンを2つ付けると値が任意になる。ただ、ちょっと問題があったのは後述 |
戻り値 | 終了時は-1が返ってくる。オプションの「値」はoptargというグローバル変数に格納されて戻ってくる。戻り値で戻ってこない。また、'?'という解析失敗時に戻ってくる値もある |
サンプルを見たほうがわかりよいのでサンプルを見てみましょう。
getopt関数のサンプル実行
まずはサンプルコード。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int ret = -1;
while (1) {
ret = getopt(argc, argv, "ca:d::");
if (ret == -1)
break;
switch (ret) {
case 'c':
printf("option c\n");
break;
case 'a':
printf("option a with value '%s'\n", optarg);
break;
case 'd':
if (optarg)
printf("option d with value '%s'\n", optarg);
else
printf("option d with value (null)\n");
break;
case '?':
printf("???\n");
break;
default:
printf("?? getopt returned character code 0%o ??\n", ret);
}
}
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc) {
printf("%s ", argv[optind++]);
}
printf("\n");
}
exit(EXIT_SUCCESS);
}
getopt()の第三引数に指定したオプション文字列 "ca:d::" は、-cというオプションと、-aという値を持つオプションと、-dという値を任意で持つオプションがあることを示している。
最後の
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc) {
printf("%s ", argv[optind++]);
}
printf("\n");
}
の部分は、オプションを付けていない、値のみのコマンドライン引数を処理している箇所である。
???
なんかコード変じゃない?
これだと、-c -a 100 zzzとかはちゃんとzzzを取ってくれそうだけど、zzz -c -a 100 とかにしたらzzzじゃなく100が戻ってきそうな気が・・・。
確かにこのコードだけ見ると全くそのように見えますね。
実はgetopt関数は中でargvを書き換えているのです。
要は、最初にmainから渡されてきたargvの中身が
argv[0]=./getopt_sample
argv[1]=zzz
argv[2]=-c
argv[3]=-a
argv[4]=100
argv[5]=-c
であったのに、getopt関数の処理がすべて終了したときには、
argv[0]=./getopt_sample
argv[1]=-c
argv[2]=-a
argv[3]=100
argv[4]=-c
argv[5]=zzz
となっているのです。
よって、このコードで正く、オプションのない値だけの引数を処理することができるのです。
ではオプションをつけていろいろ実行してみましょう。
$ ./getopt_sample -c
option c
2つオプションを付けてみる。
$ ./getopt_sample -c -a 10
option c
option a with value '10'
いい感じ。当然順番を変えても大丈夫。
$ ./getopt_sample -a 10 -c
option a with value '10'
option c
値が任意のオプション-dも試してみましょう。
$ ./getopt_sample -c -a 10 -d 20
option c
option a with value '10'
option d with value (null)
non-option ARGV-elements: 20
???
あれ?任意オプションの-dが機能してない!!
任意オプションは値をくっつけないといけない!!
任意オプションの値がちゃんと拾われていません。もう一度確認してみましょう。
$ ./getopt_sample -d 1
option d with value (null)
non-option ARGV-elements: 1
なんということでしょう。完全無視です。
実は任意の時はオプションと値をくっつけないといけないようなのです。
$ ./getopt_sample -d1
option d with value '1'
うーん。混乱します。
値が任意オプションはできるだけやめたほうが良いかもしれないですね。
getopt_long関数を使ってコマンドラインオプションを解釈する
getopt_long関数はざっくりいうとgetopt関数に長いコマンドを認識できるようにしたものです。
-hじゃなく--helpと入力できたほうが冗長かもしれませんがユーザーフレンドリーだからです。
基本的なコードはgetoptのそれをそのまま流用できます。
引数にロングオプションの情報を記述した構造体を追加してあげる必要があり、そこがgetopt関数との大きな違いです。
ただし、getopt_long関数はGNUによる拡張であるため、使えない環境もあるので注意が必要です。
引数については以下の通り。
#include <getopt.h>
int getopt_long(
int argc,
char* argv[],
const char* optstring,
const struct option* longopts,
int* longindex);
引数 | 意味 |
---|---|
argc | mainの引数ぶっこんどけばOK |
argv | mainの引数ぶっこんどけばOK |
optstring | getoptの時とおんなじ |
longopts | 以下に記すオプション構造体を設定する |
longindex | オプションの個数 |
戻り値 | longoptsのval値 |
getoptにはなかったオプション構造体が以下です。
struct option {
char* name;
int has_arg;
int* flag;
int val;
};
変数 | 意味 |
---|---|
name | ロングオプションの名前。--は付けない。--helpの場合、helpと指定 |
has_arg | 値を持つかどうかの定義。 no_argument(もしくは0だと持たない required_argument(もしくは1)だと持つoptional_argument(もしくは2)だと持っても持たななくてもよい |
flag | NULL(もしくは0)にしとくと、getopt_long()がvalを返却するようになる 0以外の、int*の変数を設定しておくとvalの値がその変数に格納され、代わりに戻り値が0になる 戻り値でvalを受け取る、で特に問題ないと思うので、この変数は常にNULLにしとけばわかりよいかも |
val | getopt_long関数の戻り値。これを短いオプションと同じにしておくとif文などの判定で同じところを通るようになる。 例えばhelpの場合、hと指定しておくと-hも--helpも同じ処理をするようにできる |
なお、この構造体に値を設定する際は、一番最後は0,0,0,0にしないといけないルールがあるので注意しましょう。
このオプション構造体を使ってロングオプションをgetopt_long関数に伝えます。
サンプル見たほうが早いのでサンプルを見てみましょう。
getopt_long関数のサンプル実行
getopt_longのサンプルコードを見てみましょう。
と言っても変わったのはoption構造体とoption_index変数の追加した程度です。
大きな差があるわけではありません。
あ、unistd.hではなく、getopt.hになる点は注意が必要です。
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
int main(int argc, char* argv[])
{
int ret = -1;
int option_index = 0;
struct option long_options[] = {
{"create", no_argument, NULL, 'c' },
{"add", required_argument, NULL, 'a' },
{"delete", optional_argument, NULL, 'd' },
{0, 0, 0, 0 }
};
while (1) {
option_index = 0;
ret = getopt_long(argc, argv, "ca:d::", long_options, &option_index);
if (ret == -1)
break;
switch (ret) {
case 'c':
printf("option c\n");
break;
case 'a':
printf("option a with value '%s'\n", optarg);
break;
case 'd':
if (optarg)
printf("option d with value '%s'\n", optarg);
else
printf("option d with value (null)\n");
break;
case '?':
printf("???\n");
break;
default:
printf("?? getopt returned character code 0%o ??\n", ret);
}
}
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc) {
printf("%s ", argv[optind++]);
}
printf("\n");
}
exit(EXIT_SUCCESS);
}
これを実行すると、--createは-cと、--addは-aと、--deleteは-dと同じ動きをします。
構造体を作らないといけないのめんどー。っていう程度の面倒さであって、基本的な実装はgetoptのままです。
optional_argument だと値がNULLになる!!
さっきも見ましたね。
getopt関数の時と同じ問題かぁ・・・。と思ったらちょっと様子が違いました。
まずはロングオプションで試す。
$ ./getopt_long_sample1 --delete 10
option d with value '(null)'
non-option ARGV-elements: 10
getoptの時と同様にくっつけてみましょう。
$ ./getopt_long_sample1 --delete10
???
???
ダメやんけワレー!解析失敗しとるがな!!
試しにgetoptでは失敗した、短いオプションで試してみましょう。
$ ./getopt_long_sample1 -d 10
option d with value '10'
??????
理解不能。
getoptの時はダメだったのに、大丈夫だった。どういうこと?
わからんのでぐぐったところ、=を付けろとのコメントが。
$ ./getopt_long_sample1 --delete=10
option d with value '10'
値が取れた!!
って取れたじゃねぇよ。意味が全くわからん。環境依存か?
混乱を招くのでgetoptやgetopt_longで任意の値をとるのはやめたほうがいい。
以上。