C言語でコマンドを作るまで

前回に続き、今度はC言語でコマンドを作ってみた。既存のコマンドにはない新しい機能が欲しかったら、多分、C言語で実装することになるのだと思う。OSXAPIを利用するにも、ObjectiveC(ベースがC言語)の流儀でアクセスすることになるし。

作業環境

  • MacBook OSX 10.6.2
  • Developer Toolsをインストール済

基本(Xcode利用)

  • Xcodeを起動する。
  • 新規 Xcode プロジェクトを作成。
  • Mac OSXのApplication >> Command Line Toolを選択して、右下の[選択...]ボタン。
  • プロジェクト名にhelloと名前を付けて保存した。
  • プロジェクトが開いたら、ビルドと実行ボタンを押す。
  • デバッガコンソールが開いて「Hello, World!」と表示された。
  • すでに、helloコマンドは出来上がっていた!
  • コンパイルされた実行コードは、~/hello/build/Debug/helloにあった。
  • もう一度、今度はターミナルから実行してみた。
$ ~/hello/build/Debug/hello
Hello, World!
  • ターミナルからやっても、ちゃんと表示された。
  • helloプロジェクトには、main.cというソースコードのファイルが一つだけあった。
#include <stdio.h>

int main (int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    return 0;
}

基本(テキストエディタ+ターミナル)

  • ソースコードは単なるテキストファイルなので、テキストエディットで書いてもOK。
  • 上記のコードをテキストエディットにコピーして、ファイル名をhello.cにして、標準テキストとして保存した。
  • hello.cをターミナルからコンパイル
$ cd ~/Desktop
$ gcc hello.c 
  • すると、デスクトップにa.outというファイルが生成された。これが、hello.cをコンパイルした実行コードになる。
$ ./a.out
Hello, World!
  • ちゃんと出来たようだ!

引数の処理

  • ~/Desktop/hello.c
#include <stdio.h>

int main (int argc, const char * argv[]) {
    int i;
    
    for (i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    // insert code here...
    printf("Hello, World!\n");
    return 0;
}
  • コンパイルして実行してみると、以下のような結果になった。
$ cd ~/Desktop
$ gcc hello.c 
$ ./a.out '山田 太郎' '伊藤 四郎' '鈴木 一郎'
argv[0] = ./a.out
argv[1] = 山田 太郎
argv[2] = 伊藤 四郎
argv[3] = 鈴木 一郎
Hello, World!

なるほど!

オプションの解析

  • シェルスクリプトの時と同様に、引数を見て、オプション設定を解析するのは手間のかかる作業だ。
  • そんな訳で、C言語にもgetopt()関数が用意されていた。
/* getopt()は、解析したオプションを一文字ずつ返す、返せる文字がなくなると-1を返す
    例:say -o hi.aac Hello, World
        -o      オプション
        hi.aac  オプション引数
        Hello,  コマンド引数  <---この時点で-1を返す
        World   コマンド引数
 */

/* getopt()では、以下のグローバル変数が宣言されている
 extern char *optarg;   // オプション引数
 extern int optind;     // 解析中の引数を指すargvのインデックス
 extern int opterr;     // 0を設定すると、エラーメッセージを出力しない
 extern int optopt;     // エラーが発生したオプション文字
 */

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

int main(int argc, char * argv[]){
    int opt, i;
    char * sentence = "hello";
    char * suffix = "";
    
    //opterr = 0;/* エラーメッセージを非表示にする */
    
    while((opt = getopt(argc, argv, "mes:")) != -1){
        switch(opt){
            case 'm':
                sentence = "good morning";
                printf("  Option -%c\n", opt);
                break;
            case 'e':
                sentence = "good evening";
                printf("  Option -%c\n", opt);
                break;
            case 's':
                suffix = optarg;
                printf("  Option -%c = %s\n", opt, optarg);
                break;
            
            // 解析できないオプションが見つかった場合は「?」を返す
            // オプション引数が不足している場合も「?」を返す
            case '?':
                printf("Unknown or required argument option -%c\n", optopt);
                printf("Usage: COMMAND [-m | -e] [-s suffix] name ...\n");
                return 1;
        }
    }
    // insert code here...
    for(i = optind; i < argc; i++){
        printf("%s, %s%s!\n", sentence, argv[i], suffix);
    }
    return 0;
}
  • コンパイルして実行してみると、以下のような結果になった。
$ cd ~/Desktop
$ gcc hello.c 
$ ./a.out -esさん '山田 太郎' '伊藤 四郎' '鈴木 一郎'
  Option -e
  Option -s = さん
good evening, 山田 太郎さん!
good evening, 伊藤 四郎さん!
good evening, 鈴木 一郎さん!
  • 存在しない引数はエラー表示
    • ./a.out: illegal option -- a は、getopt()のエラー表示(opterr = 0;で非表示にできる)
    • Unknown or required argument option -a 以下は、自前のエラー表示
$ ./a.out -easさん '山田 太郎' '伊藤 四郎' '鈴木 一郎'
  Option -e
./a.out: illegal option -- a
Unknown or required argument option -a
Usage: COMMAND [-m | -e] [-s suffix] name ...
  • 引数が不足している場合のエラー表示
    • ./a.out: option requires an argument -- s は、getopt()のエラー表示(opterr = 0;で非表示にできる)
    • Unknown or required argument option -s 以下は、自前のエラー表示
$ ./a.out -s
./a.out: option requires an argument -- s
Unknown or required argument option -s
Usage: COMMAND [-m | -e] [-s suffix] name ...

これで、前回のシェルスクリプト版と同じ仕様になった。但し、1文字オプション限定なのだ。

複数文字の長いオプションに対応

  • さらに、c言語にはgetopt_long()が用意されている。
  • getopt_long()なら、複数文字の長いオプションも楽して解析できるのだ!素晴らしい!
/* getopt()は、解析したオプションを一文字ずつ返す、返せる文字がなくなると-1を返す
    例:say -o hi.aac Hello, World
        -o      オプション
        hi.aac  オプション引数
        Hello,  コマンド引数  <---この時点で-1を返す
        World   コマンド引数
 */

/* getopt()では、以下のグローバル変数が宣言されている
 extern char *optarg;   // オプション引数
 extern int optind;     // 解析中の引数を指すargvのインデックス
 extern int opterr;     // 0を設定すると、エラーメッセージを出力しない
 extern int optopt;     // エラーが発生したオプション文字
 */

/* getopt_long()では、さらに以下のoption構造体が定義されている
 struct option {
    const char *name;   // 長いオプション名
    int has_arg;        // オプションが値を持つかどうか___(1)___
    int *flag;          // NULLなら、valを返す(変数ポインタ&someを設定すると、some = valになる)
    int val;            // オプションが見つかった時に返す値(flagが指す変数に代入する値)
 };
 
 長いオプションを発見すると...
    - flagが変数ポインタ&someの場合、 getopt_long()は0を返し、some = val (someは任意の変数)
    - flagがNULLの場合、getopt_long()はvalを返す
 
 ___(1)___has_argは、以下の定数を持つ
 enum{
    no_argument         = 0,    // オプション引数なし
    required_argument   = 1,    // オプション引数必要
    optional_argument   = 2     // オプション引数どちらでもOK
 };
 
 */

#include <stdio.h>      /* printf() */
#include <stdlib.h>     /* exit() */
#include <unistd.h>     /* ?????? */
#include <getopt.h>     /* getopt_long() */

int main(int argc, char * argv[]){
    int opt, i, option_index;
    char * sentence = "hello";
    char * suffix = "";
    struct option long_options[] = {
        {"morning", no_argument,        NULL, 'm'},
        {"evening", no_argument,        NULL, 'e'},
        {"suffix",  required_argument,  NULL, 's'},
        {0, 0, 0, 0}// 配列の最後はすべて0で埋める
    };
    
    //opterr = 0;/* エラーメッセージを非表示にする */
    
    //while((opt = getopt(argc, argv, "mes:")) != -1){
    while((opt = getopt_long(argc, argv, "mes:", long_options, &option_index)) != -1){
        switch(opt){
            case 'm':
                sentence = "good morning";
                printf("  Option -%c\n", opt);
                break;
            case 'e':
                sentence = "good evening";
                printf("  Option -%c\n", opt);
                break;
            case 's':
                suffix = optarg;
                printf("  Option -%c = %s\n", opt, optarg);
                break;
            
            // 解析できないオプションが見つかった場合は「?」を返す
            // オプション引数が不足している場合も「?」を返す
            case '?':
                printf("Unknown or required argument option -%c\n", optopt);
                printf("Usage: COMMAND [-m | -e] [-s suffix] name ...\n");
                return 1;   // exit(EXIT_FAILURE);と同等 http://okwave.jp/qa/q794746.html
        }
    }
    
    for(i = optind; i < argc; i++){
        printf("%s, %s%s!\n", sentence, argv[i], suffix);
    }
    return 0;   // exit(EXIT_SUCCESS);と同等 http://okwave.jp/qa/q794746.html
}
  • コンパイルして実行してみると、長いオプション名の処理は、1文字オプションと統合しているので、区別なく同じ表示。
$ cd ~/Desktop
$ gcc hello.c 
$ ./a.out -evening --suffix=さん '山田 太郎' '伊藤 四郎' '鈴木 一郎'
  Option -e
  Option -s = さん
good evening, 山田 太郎さん!
good evening, 伊藤 四郎さん!
good evening, 鈴木 一郎さん!
  • long_options[].flagに変数アドレスを設定すれば、長いオプションと、1文字オプションを明確に区別できる。
#include <stdio.h>      /* printf() */
#include <stdlib.h>     /* exit() */
#include <unistd.h>     /* ?????? */
#include <getopt.h>     /* getopt_long() */

int main(int argc, char * argv[]){
    int opt, i, option_index, long_opt;
    char * sentence = "hello";
    char * suffix = "";
    /*
     struct option long_options[] = {
        {"morning", no_argument,        NULL, 'm'},
        {"evening", no_argument,        NULL, 'e'},
        {"suffix",  required_argument,  NULL, 's'},
        {0, 0, 0, 0}// 配列の最後はすべて0で埋める
     };
     */
    struct option long_options[] = {
        {"morning", no_argument,        &long_opt, 'm'},
        {"evening", no_argument,        &long_opt, 'e'},
        {"suffix",  required_argument,  &long_opt, 's'},
        {0, 0, 0, 0}// 配列の最後はすべて0で埋める
    };
    
    //opterr = 0;/* エラーメッセージを非表示にする */
    
    //while((opt = getopt(argc, argv, "mes:")) != -1){
    while((opt = getopt_long(argc, argv, "mes:", long_options, &option_index)) != -1){
        switch(opt){
            case 0:
                switch (long_opt) {
                    case 'm':
                        sentence = "good morning";
                        printf("  Long  --%s\n", long_options[option_index].name);
                        break;
                    case 'e':
                        sentence = "good evening";
                        printf("  Long  --%s\n", long_options[option_index].name);
                        break;
                    case 's':
                        suffix = optarg;
                        printf("  Long  --%s = %s\n", long_options[option_index].name, optarg);
                        break;
                }
                break;

            case 'm':
                sentence = "good morning";
                printf("  Option -%c\n", opt);
                break;
            case 'e':
                sentence = "good evening";
                printf("  Option -%c\n", opt);
                break;
            case 's':
                suffix = optarg;
                printf("  Option -%c = %s\n", opt, optarg);
                break;
            
            // 解析できないオプションが見つかった場合は「?」を返す
            // オプション引数が不足している場合も「?」を返す
            case '?':
                printf("Unknown or required argument option -%c\n", optopt);
                printf("Usage: COMMAND [-m | -e] [-s suffix] name ...\n");
                return 1;   // exit(EXIT_FAILURE);と同等 http://okwave.jp/qa/q794746.html
        }
    }
    
    for(i = optind; i < argc; i++){
        printf("%s, %s%s!\n", sentence, argv[i], suffix);
    }
    return 0;   // exit(EXIT_SUCCESS);と同等 http://okwave.jp/qa/q794746.html
}
  • コンパイルして実行してみると、長いオプション名として表示されている。(case 0:が処理された結果)
$ cd ~/Desktop
$ gcc hello.c 
$ ./a.out --morning --suffix=さん '山田 太郎' '伊藤 四郎' '鈴木 一郎'
  Long  --morning
  Long  --suffix = さん
good morning, 山田 太郎さん!
good morning, 伊藤 四郎さん!
good morning, 鈴木 一郎さん!
  • しかし、意地悪して-morningを指定すると、エラーになる...。
$ ./a.out -morning --suffix=さん '山田 太郎' '伊藤 四郎' '鈴木 一郎'
  Option -m
a.out: invalid option -- o
Unknown or required argument option -o
Usage: COMMAND [-m | -e] [-s suffix] name ...
  • 長いオプションの先頭には、--が付加されるのが原則なので、しょうがない、という考えも出来る。
  • しかし、最近はその原則が破られつつある。-morning的な長いオプションも遠慮なく使われている。
  • そんな時は、getopt_long()を、getopt_long_only()に(関数名だけ)置き換えると...
$ cd ~/Desktop
$ gcc hello.c 
$ ./a.out -morning -suffix=さん '山田 太郎' '伊藤 四郎' '鈴木 一郎'
  Long  --morning
  Long  --suffix = さん
good morning, 山田 太郎さん!
good morning, 伊藤 四郎さん!
good morning, 鈴木 一郎さん!

対応できた!


これでどうにか、コマンドを作る時の雛形は出来上がったのだ。あとは中身が問題だ。

参考ページ

以下のページが大変参考になりました。感謝です!