Shellとシステム(2)

このエントリーをはてなブックマークに追加

Shellとシステム(2)

前回はmain関数の戻り値によってプログラムの終了状態をShellに通知する方法を解説しました。
今回は逆にShell側からプログラムに情報を伝える方法を解説します。
これには大きく3つのやり方があります。1つ目はmain引数、2つ目は環境変数、3つめは設定ファイルです。
ここでは、まずmain引数の扱いについて解説します。

main引数

argc,argv

さて、まずは基本から。mainの引数は伝統的に、argc、argvが使われています。
これは主にプログラムへのオプション指定、ファイル指定等に使われます。
argcはプログラム自身を含めた実行時の単語数、つまり引数の数+1です。
argvはプログラム自身を含めた実行時の文字列が入ります。

#include <stdio.h>

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

        printf("argc is %d\n", argc);

        printf("argv\n");
        for(i=0; i<argc; i++) {
                printf("%s\n", argv[i]);
        }

        return 0;
}

実行してみましょう。
$ ./arg abc def ghi
argc is 4
argv
./arg
abc
def
ghi

"argv[0]"にはプログラム名が入り、"argv[1]"以降が引数となっていることが分かります。


argv[0]の利用方法

"argv[1]"以降は利用方法が想像できますが、"argv[0]"をどのように利用するかがピンと来ない方が多いと思います。
例えば、エディタとして"vim"がありますが、"view"というコマンドもありこれは"vim -R"と同じ動作(読み込み専用)となります。
実は、"view"は"vim"へのシンボリックリンクとなっています。

$viewはvimへのシンボリック
$ which view
/usr/bin/view
$ ls -l /usr/bin/view
lrwxrwxrwx 1 root root 22  2月 23 02:43 /usr/bin/view -> /etc/alternatives/view

$vimも実はシンボリックリンク。
$ which vim
/usr/bin/vim
$ ls -l /usr/bin/vim
lrwxrwxrwx 1 root root 21  2月 23 02:56 /usr/bin/vim -> /etc/alternatives/vim

これはどういうことでしょうか?
実はvimは"argv[0]"の内容を見て動作を変えているのです。
他にも"ex"も同じくvimへのシンボリックリンクです。

また、"argv[0]"は"ps"で出てくるコマンド名にもなりますが、"argv"の内容は書き換えることも可能なので、実行後にここを変更することも実は可能です。
これを悪用する場合もあり、ネットワークから不正侵入して密かに裏で動かすプログラムはこのような動作をする場合もあります。
セキュリティ知識も大事ですのでこういった知識も勉強ましょう。ただし、悪用はしないように!


main引数のパース

getopt(3)

基本的なargc,argvはこれで理解したと思います。しかし実際のコマンドを見ると非常にオプションが多く現実のコマンドと同様の処理を行おうとするとかなり大変な事が分かります。

そこで、利用するのがgetopt(3)、getopt_long(3)です。getopt(3)はPOSIXで定義されているので移植性はあります。
まずはgetopt(3)のサンプルから紹介しましょう。せっかくmanにあるのでそれを引用します。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    int flags, opt;
    int nsecs, tfnd;

    nsecs = 0;
    tfnd = 0;
    flags = 0;
    while ((opt = getopt(argc, argv, "nt:")) != -1) {
        switch (opt) {
        case 'n':
            flags = 1;
            break;
        case 't':
            nsecs = atoi(optarg);
            tfnd = 1;
            break;
        default: /* '?' */
            fprintf(stderr, "Usage: %s [-t nsecs] [-n] name\n",
            argv[0]);
            exit(EXIT_FAILURE);
        }
    }

    printf("flags=%d; tfnd=%d; optind=%d\n", flags, tfnd, optind);

    if (optind >= argc) {
        fprintf(stderr, "Expected argument after options\n");
        exit(EXIT_FAILURE);
    }

    printf("name argument = %s\n", argv[optind]);
    if (optind >= argc) {
        fprintf(stderr, "Expected argument after options\n");
        exit(EXIT_FAILURE);
    }

    printf("name argument = %s\n", argv[optind]);

    /* Other code omitted */

    exit(EXIT_SUCCESS);
}

オプションは次の通りです
まず、このプログラムは"-t"か"-n"の任意のオプションがあり、さらにデフォルトで"name"オプションが必要となります。
"-n"オプション。これは引数がありません。
"-t"オプション。これは引数を1つ必要とします。
では早速実行して見ましょう。
#引数、オプションなし。エラーとなります
$ ./getopt
flags=0; tfnd=0; optind=1
Expected argument after options

#引数あり、オプションなし。正常終了します。
$ ./getopt abcdefg
flags=0; tfnd=0; optind=1
name argument = abcdefg
name argument = abcdefg

#引数なし、オプションはn。エラーとなります。
$ ./getopt -n
flags=1; tfnd=0; optind=2
Expected argument after options

#引数あり、オプションはn。正常終了します。
$ ./getopt -n abcdefg
flags=1; tfnd=0; optind=2
name argument = abcdefg
name argument = abcdefg

#引数なし、オプションはt、オプション引数なし。エラーとなります。
$ ./getopt -t 
./getopt: option requires an argument -- 't'
Usage: ./getopt [-t nsecs] [-n] name

#引数なし、オプションはt、オプション引数あり。エラーとなります。
$ ./getopt -t 1
flags=0; tfnd=1; optind=3
Expected argument after options

#引数あr、オプションはt、オプション引数あり。正常終了します。
$ ./getopt -t 1 abcdefg
flags=0; tfnd=1; optind=3
name argument = abcdefg
name argument = abcdefg

簡潔に書くことができ、すべて正しく判断しています。
ちなみに、getopt(3)はよくある長いオプション(例えば、"command -f"と同様の"command --file"といった形です)をサポートしていません。


getopt_long(3)

getopt(3)は非常に便利ですが、ロングオプションに対応していない事が欠点でした。
getopt_long(3)はこの問題点に対応しており、getopt(3)に加えてロングオプションにも対応しています。ただし、getopt_long(3)はGNU拡張です。
では、またmanから引用してみます。

#include <stdio.h>    /* for printf */
#include <stdlib.h>    /* for exit */
#include <getopt.h>

int main(int argc, char **argv) {
    int c;
    int digit_optind = 0;

    while (1) {
        int this_option_optind = optind ? optind : 1;
        int option_index = 0;
        static struct option long_options[] = {
            {"add",     required_argument, 0,  0 },
            {"append",  no_argument,       0,  0 },
            {"delete",  required_argument, 0,  0 },
            {"verbose", no_argument,       0,  0 },
            {"create",  required_argument, 0, 'c'},
            {"file",    required_argument, 0,  0 },
            {0,         0,                 0,  0 }
        };

        c = getopt_long(argc, argv, "abc:d:012",
                        long_options, &option_index);
        if (c == -1)
            break;

        switch (c) {
            case 0:
                printf("option %s", long_options[option_index].name);
                if (optarg)
                    printf(" with arg %s", optarg);
                printf("\n");
                break;

            case '0':
            case '1':
            case '2':
                if (digit_optind != 0 && digit_optind != this_option_optind)
                    printf("digits occur in two different argv-elements.\n");
                digit_optind = this_option_optind;
                printf("option %c\n", c);
                break;

            case 'a':
                printf("option a\n");
                break;

            case 'b':
                printf("option b\n");
                break;

            case 'c':
                printf("option c with value '%s'\n", optarg);
                break;

            case 'd':
                printf("option d with value '%s'\n", optarg);
                break;

            case '?':
                break;

            default:
                printf("?? getopt returned character code 0%o ??\n", c);
        }
    }

    if (optind < argc) {
        printf("non-option ARGV-elements: ");
        while (optind < argc)
            printf("%s ", argv[optind++]);
        printf("\n");
    }

    exit(EXIT_SUCCESS);
}

実行してみると通常のオプションとロングオプションの両方を受け入れていることが分かります。
$ ./getopt_long -0
option 0
$ ./getopt_long -1
option 1
$ ./getopt_long -2
option 2
$ ./getopt_long -a
option a
$ ./getopt_long --add abcdefg
option add with arg abcdefg


まとめ

さていかがでしたでしょうか。argc、argvという基本的な部分ですが実用プログラムを作る場合には必須の知識です。
getopt(3)、getopt_long(3)以外にもgetopt_long_only(3)、getsubopt(3)というバージョンもあります。
これらを使用して楽でバグのないプログラムを作りたいものですね。