Help us understand the problem. What is going on with this article?

[C/C++]プログラムにコマンドラインからUNIX風のオプションを渡す方法(getopt関数)

More than 3 years have passed since last update.

はじめに

自分で作ったCのプログラムにUNIXライクなオプションを与えて動作を変えたいと思ったことはないだろうか?

おそらくまず思いつく方法がコマンドライン引数(main関数に渡す引数のこと)をargcの数だけstrcmpで比較する方法だろう。もちろんこの方法でも実現することはできるが、もっと簡単に実装できる便利な関数があるので紹介する。

getopt関数

概要

unistd.h
int getopt(int argc, char * const argv[], const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;

getopt関数とはコマンドライン引数のオプション(ハイフン'-'で始まる文字)を解析する関数である。
unistd.hをインクルードして使う。
第1、第2引数は、main関数の引数であるargc,argvをそのまま渡し、第3引数には使用するオプション文字の集合を文字列にして渡す。
返り値で返される値には大きく分けて3つのパターンがある。

  • optstringで指定したオプション文字が認識された場合、そのオプション文字を返す。
  • 指定していないオプション文字が認識された場合、または、引数が必要なオプションに引数を渡さなかった場合、'?'を返す。
  • オプション文字以外の引数(ハイフン'-'がついてないコマンドライン引数)が認識された場合、-1を返す。

なお、返り値は全て整数型となる。

optstringの書き方

getopt()の第3引数には、使用したいオプション文字の集合をつなげて文字列にして渡す。

getopt(argc, argv, "fgh:");

例えばf,g,hをオプションに使いたい場合は、上記の様に記述する。
':'をつけることで、その直前の文字を"引数を取るオプション文字"とすることができる。

4つのグローバル変数について

この関数は、*optarg, optind, opterr, optoptという4つのグローバル変数を利用している。

  • *optarg - getopt()が引数付きのオプションを検知した場合、その引数が格納される。もし、引数を取らないオプションを検知した場合は、NULLが格納される。
  • optind - 初期値は1。getopt()がargv[]を一つ解析するたびに1ずつ増えていく。getopt()はargv[getopt]を解析することでargv[]を順々に解析することができる。
  • opterr - 初期値は1。opterr = 0 にすると、getopt()が出す標準エラー出力が無効化される。
  • optopt - 初期値は0。解析したオプション文字が格納される。

使用例

sample.c
#include <stdio.h>
#include <unistd.h>

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

    opterr = 0; //getopt()のエラーメッセージを無効にする。

    while ((opt = getopt(argc, argv, "fgh:")) != -1) {
        //コマンドライン引数のオプションがなくなるまで繰り返す
        switch (opt) {
            case 'f':
                printf("-fがオプションとして渡されました\n");
                break;

            case 'g':
                printf("-gがオプションとして渡されました\n");
                break;

            case 'h':
                printf("-hがオプションとして渡されました\n");
                printf("引数optarg = %s\n", optarg);
                break;

            default: /* '?' */
                //指定していないオプションが渡された場合
                printf("Usage: %s [-f] [-g] [-h argment] arg1 ...\n", argv[0]);
                break;
        }
    }

    //オプション以外の引数を出力する
    for (i = optind; i < argc; i++) {
        printf("arg = %s\n", argv[i]);
    }

    return 0;
}

上記のプログラム(sample.c)はオプションとオプション以外のコマンドライン引数を区別して出力するプログラムである。
オプション文字かそれ以外の引数かを判別することができる。
-f,-g,-hがオプション文字となるが、-hは引数を取るオプション文字となっている。

入力例1:-f -g aaa bbb

$ ./sample -f -g aaa bbb
-fがオプションとして渡されました
-gがオプションとして渡されました
arg = aaa
arg = bbb

オプション文字かどうか判別できている。

入力例2:-fg aaa bbb

$ ./sample -fg aaa bbb
-fがオプションとして渡されました
-gがオプションとして渡されました
arg = aaa
arg = bbb

-fgという風につなげても認識する。

入力例3:-fh aaa bbb

$ ./sample -fh aaa bbb
-fがオプションとして渡されました
-hがオプションとして渡されました
引数optarg = aaa
arg = bbb

-hは引数を取るオプションなので、"aaa" が-hに対する引数として処理されている。

入力例4:-o aaa bbb

$ ./sample -o aaa bbb
Usage: ./sample [-f] [-g] [-h argment] arg1 ...

指定していないオプションを入力すると、getopt()は'?'を返す。

入力例5:aaa bbb -f

$ ./sample aaa bbb -f
arg = aaa
arg = bbb
arg = -f

引数より後にオプションを持ってきてしまった場合、ただの引数として処理されてしまうので注意が必要。

入力例6:-hgf

$ ./sample -hgf
-hがオプションとして渡されました
引数optarg = gf

ユーザが誤って引数が必要なオプションに引数を渡さず、オプションを渡してしまった場合、getopt()ではその判断ができないので注意が必要。

ハイフンで始まる値をオプションでない引数としたい場合

入力例7:-f -100

./sample -f -100
-fがオプションとして渡されました
Usage: ./getopt_func [-f] [-g] [-h argment] arg1 ...

"-100"を入力したくても、オプションと判断されてしまうため、期待と違う結果となってしまう。そういう場合は下のように入力する。

入力例8:-f -- -100

./sample -f -- -100
-fがオプションとして渡されました
arg = -100

"--"をオプションの後に入れることで、getopt()にオプションの入力は終わったと判断させることができ、"-100"といった値も入力できる。

opterrの値を1にするとどうなるか

sample.cのopterr = 0;の部分をコメントアウトする。

sample.c
#include <stdio.h>
#include <unistd.h>

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

    //opterr = 0; //getopt()のエラーメッセージを無効にする。
~
~ 以下省略
~

入力例9:-o aaa

$ ./sample -o aaa
./sample: illegal option -- o
Usage: ./sample [-f] [-g] [-h argment] arg1 ...

opterrの値を変更しない場合(=1)、getopt()は上のようなエラーメッセージを標準エラー出力に表示する。

注意点

  • sample.cのようにgetopt() != -1を条件にループさせた場合、引数の後にオプションを与えてしまったら、オプションを引数として受け取ってしまう。(入力例5を参照)
  • 引数を取るオプションのすぐ後にオプションを与えてしまった場合、そのオプション自体を引数として取ってしまうため、思わぬバグが発生する可能性がある。(入力例6を参照)
  • ハイフン2つと単語で構成されるオプションは使えない。(例:--name)
  • 環境によってgetopt関数の仕様が違う可能性があるため、まずは動作を確認すること。

getopt_long関数とgetopt_long_only関数について

getopt関数では、ハイフン2つと単語で構成されるオプションは使えないが、これを使えるようにしたのがgetopt_long関数とgetopt_long_only関数である。
この2つの関数は、getopt関数の機能に加えて、"--name"といったオプションも判別できるようになっている。それぞれの違いはここでは省略する。
後日、これらの関数についての記事も投稿したい。

環境

$ gcc -v
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.3.0 (clang-703.0.31)
Target: x86_64-apple-darwin15.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

参考

こちらのサイトを参考に書かせていただきました。ありがとうございましたm(_ _)m

Shitimi_613
サイボウズ株式会社の新人です。
https://korosuke613.github.io
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした