C言語でコマンドを作るまで
前回に続き、今度はC言語でコマンドを作ってみた。既存のコマンドにはない新しい機能が欲しかったら、多分、C言語で実装することになるのだと思う。OSXのAPIを利用するにも、ObjectiveC(ベースがC言語)の流儀でアクセスすることになるし。
基本(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!
なるほど!
オプションの解析
/* 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, 鈴木 一郎さん!
対応できた!
これでどうにか、コマンドを作る時の雛形は出来上がったのだ。あとは中身が問題だ。
参考ページ
以下のページが大変参考になりました。感謝です!