はじめに
私は Linux も C 言語もほぼ知らないですが今この本を読んで勉強中です。
ふつうのLinuxプログラミング 第2版 Linuxの仕組みから学べるgccプログラミングの王道
ざっくり言うと cat, ls コマンドとかを自作して Linux プログラミングについて学ぼう!っていう本です(まだ途中までしか読んでない)。
この本を読んでて、標準入出力とオプション解析でわりとコマンドで遊べるんじゃないかなと思ったので以前 Qiita でトレンド入りしてたこちらを作ってみたいと思います。
カフェでプログラミングしてる風(でも何もやってない)Java(クソ)コード
ラズパイを使うのは Raspbian に gcc が入ってるのと Raspbian も Linux だから!
gccの使い方
gcc は C のコンパイラです。
とりあえず Hello, World! をやってみます。
1 2 3 4 5 6 |
#include <stdio.h> int main(int argc, char *argv[]) { printf("Hello, World!\n"); return 0; } |
- 上記を hello.c で保存する。
- 下記コマンドでビルドする。
1$gcc -Wall -o hello hello.c - 下記コマンドで実行する。
1$./hello
-Wall
オプションはコードチェックをしてくれるやつなのでとりあえず常に付けとけば OK(この変数使ってないよ!とか警告してくれる)。
-o
オプションはプログラムに名前を付けるために使う。付けない場合は常に a.out で作成される。
コマンドライン引数
1 |
int main(int argc, char *argv[]) |
上記の argc
は引数の数、argv
は引数の実体です。
argv[0]
には毎回プログラム名が入ってる(上記の hello の場合、./hello になる)。
参考
- 第1章 Linux プログラミングを始めよう
標準入出力
すべてのプロセス(動作中のプログラム)に用意されているストリーム(バイト列の通り道)は下記3つ。
- 標準入力
キーボード、別プロセスとかにつながってる。 - 標準出力
ディスプレイ、別プロセスとかにつながってる。 - 標準エラー出力
標準出力は別プロセスにつながってることがあるので別枠の出力。
すべてのストリームにはファイルディスクリプタという整数値が割り当てられていてプログラムからストリームを扱う場合はこのファイルディスクリプタを利用する。
標準入出力には標準入出力ライブラリ(stdio:standard I/O)を使う模様。
使い方
下記のようにするとパラメータで受けたファイルの中身を出力する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "wrong argument\n"); exit(1); } FILE *f; int c; // 読み取り専用でファイル開く f = fopen(argv[1], "r"); if (!f) { // ファイル開くの失敗した場合 // errnoの値を出力する perror(argv[1]); exit(1); } // EOF(End of Fileになるまで読み込み) while ((c = fgetc(f)) != EOF) { // 標準出力に書き込む、エラーの場合終了 if (fputc(c, stdout) < 0) exit(1); } fclose(f); exit(0); } |
参考
- 第5章ストリームにかかわるシステムコール
- 第6章ストリームにかかわるライブラリ関数
オプション解析
コマンドのオプションにはショートプションとロングオプションのパラメータあり、なしがある。
以下は ls -a -s -k と同じ。
・ls -ask
・ls --all --size --kibibytes
以下は head -n 5 と同じ。
・head -n5
・head --line 5
・head --line=5
マイナス始まりは単なる慣習だけど新しいコマンドはだいたい慣習に従っているらしい。
オプション解析には getopt_long
を使う。定義は下記。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <getopt.h> // 現在処理中のオプションのパラメータ extern char *optarg; // 現在処理中のオプションのargvでのインデックス extern int optind; // 現在処理中のオプション文字 extern int optopt; // 真の場合にエラーを出力する extern int opterr; // 複数回呼び出すときに使うやつ? extern int optreset; /// @param argc コマンドライン引数の数 /// @param argv コマンドライン引数の実体 /// @param optstring 解析したいオプションの文字列(ex. -a, -xなら"ax"を指定) /// @param longopts 解析するロングオプションの定義 /// @param longindex 発見したロングオプションのlongoptsでのインデックスを返す int getopt_long(int argc, char * const *argv, const char *optstring, const struct option *longopts, int *longindex); struct option { // ロングオプション名 char *name; // パラメータをとるかどうか // no_argument: パラメータなし // required_argument: 必ずパラメータをとる // optional_argument: パラメータをとるかもしれない int has_arg; // NULL: このオプション発見時にvalの値を返す // NULL以外: このオプション発見時に0を返し、*flagにvalの値を代入する? int *flag; // flagで指定されたところに返す値 int val; }; |
使い方
ソースはコマンドラインオプションの処理を参考にしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include <stdio.h> #include <stdlib.h> #include <getopt.h> int main(int argc, char *argv[]) { int aopt = 0; int bopt = 0; int copt = 0; int dopt = 0; char *cparam = NULL; char *dparam = NULL; // ロングオプションの設定 // 末尾は0を設定する struct option longopts[] = { { "add", no_argument, NULL, 'a' }, { "break", no_argument, NULL, 'b' }, { "clear", required_argument, NULL, 'c' }, { "delete", optional_argument, NULL, 'd' }, { 0, 0, 0, 0 }, }; int opt; // オプション解析 while ((opt = getopt_long(argc, argv, "abc:d::", longopts, NULL)) != -1) { switch (opt) { case 'a': // addオプションがある場合 aopt = 1; break; case 'b': // breakオプションがある場合 bopt = 1; break; case 'c': // clearオプションがある場合 copt = 1; cparam = optarg; break; case 'd': // deleteオプションがある場合 dopt = 1; dparam = optarg; break; default: // case '?'でもOK? fprintf(stderr, "error! \'%c\' \'%c\'\n", opt, optopt); exit(1); } } printf("a = %d\n", aopt); printf("b = %d\n", bopt); printf("c = %d, %s\n", copt, cparam); printf("d = %d, %s\n", dopt, dparam); exit(0); } |
1 |
$./opt -a -b -c4 -d10 |
上記を実行するとこうなる。
1 2 3 4 |
a = 1 b = 1 c = 1, 4 d = 1, 10 |
参考
- 第7章 head コマンドを作る
プログラミングしてる風(でも何もやってない)コマンドをつくる
本命のコマンドをつくっていきます。ソースは下記。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#include <stdio.h> #include <stdlib.h> #include <getopt.h> #include <sys/time.h> #define MILLI_SEC 1000000 int main(int argc, char *argv[]) { int x = 50; int y = 0; struct timespec req = {0, 100 * MILLI_SEC}; struct option longopts[] = { { "num", required_argument, NULL, 'n' }, { "random", no_argument, NULL, 'r' }, { 0, 0, 0, 0 }, }; int opt; int n = 0; int r = 0; int cnt = 0; while ((opt = getopt_long(argc, argv, "n:r", longopts, NULL)) != -1) { switch (opt) { case 'n': n = atol(optarg); break; case 'r': r = 1; break; default: fprintf(stderr, "error! \'%c\' \'%c\'\n", opt, optopt); exit(1); } } while (1) { if(x != y) { printf("#"); fflush(stdout); y++; } else { printf(" done!!\n"); fflush(stdout); cnt++; y = 0; } if (n > 0) { if (n == cnt) { exit(0); } } if (r == 1) { req.tv_nsec = (long)((rand() % 50 + 1) * 10) * MILLI_SEC; } nanosleep(&req, NULL); } exit(0); } |
回数指定(n)と#表示タイミングをランダムにする(r)オプションをつけてみました。
こんな感じです。
おわりに
もうちょっとスリープタイミングを調整すればもっとそれっぽくなりそうですがとりあえず動くやつはできました。
「第10章ファイルシステムにかかわる API」まで読んだので、現段階でもわりとコマンドで遊べそうです。
コメント
[…] 必要そうな知識のざっくりまとめです。 gcc やオプションに関しては前回の記事をどうぞ。 […]
[…] ラズパイでgccを使ってコマンドをつくる […]