はじめての C

C programming note*1

たとえば、「入力テキストの中で "the" という 文字列が 含まれている 行だけを 選んで 出力する (そうでない行は 出力しない) という 処理は、いろんな場面で ありそうな処理だ。

入力が テキストであるときに、それを 1文字ずつ 処理して言ったほうが 都合のよい 場面も もちろん 多い。 だが ... このテーマに 関するような プログラムでは 「行単位」での 入出力をするほうが ずっと 効率が よい。(p158)

UNIX の コマンド grep ですね。 たしか grep は "global regular expression and print" の 略だったと 思うけど ...。
プログラム全体の 骨組みは cat3r.c を 使うことにして、

また、ここでは 問題を 簡単にするために、「1行は 80 Byte 以下で なければならない」 という 制限を つけてある。

まず、各行中に キーワード (この例だと "the") が 含まれていれば 標準出力する 関数を つくっていきます、

#include 

#define KEYWORD "the"
#define MAX_SIZE (80 + 1 + 1) /* plus '\n' + '\0') */

void find(FILE *fp)
{
char buf[MAX_SIZE];

while (fgets(buf, MAX_SIZE, fp) != NULL) {
if (strstr(buf, KEYWORD))
fputs(buf, stdout);
}
}

fgets() で 1行ごとに 標準入力から 読み、(if の 条件文で キーワードを 探してから) fputs() で 行単位で 標準出力に 書いている。

この関数で はじめて 使われた 標準ライブラリ関数の strstr() ですが、

この関数を 使うときには、string.h を インクルードしなければならない。string.h の中には、

char *strstr(const char *cs, const char *ct);

というような プロトタイプ宣言が 含まれているはずだ。 この const という C の キーワードについては ここでは 説明しないので、

char *strstr(char *cs, char *ct);

とでも 書いてあると 考えてよい。

strstr() 関数は、文字列 cs の中から 文字列 ct を 探し、見つかれば 最初に 現れる 位置への ポインタを、見つからないときには NULL を 返す。

ということは、1つめの 引数に 1行分のデータを、2つめの 引数に 探そうとしている単語を 指定して strstr() 関数を 呼び出せば、その単語が 含まれている 行かどうかを 検査できる。(p160-161)

このままでも プログラムは 組み立てられますが、少し 改良を 加えていきます。まず キーワードは #define で 変更可能ですが、このままだと、

変更のたびに いちいち プログラムを 修正して コンパイルしなおさないと、新しい 検索対象に 対応した 実行用ファイルを 作れないのだ。

つまり、

このような ときには、1つの プログラムで 「実行時に 指定した 任意の文字列を 探せる」 ように するべきなのだ。(p162)

そのためには、以前 つくったように、main() 関数の中に パラメータを 指定するためのコードを 追加する 必要が あります。
始めは、オプションを 使わず キーワードが 1つだけの 場合、

検索語 (探すべき 文字列) の 指定は (コマンドラインの) 第1パラメータで 行なうことにする。

つまり、実行時に そこに 指定した文字列を 探せるように するのだ。

第2パラメータ 以降が 入力ファイル名で、その順に 処理する。

また パラメータが 1つしか ないとき (ファイル名の 指定が 1つも ないとき) には、標準入力を 処理する。

パラメータが 1つも ないときには 使用法のヒントを 標準エラー出力に 表示して 終了することにしよう。(p163)

こんなふうな 指定の やりかたです、

$ ./find1 keyword file
または、
$ ./find1 keyword < file
続けて ファイルを 読み込むには、
$ ./find1 keyword file1 file2 file3

*1:「作ってわかる Cプログラミング」