引き続き「ふつうの Linux プログラミング 第2版」を読んでいく。6章の練習問題に次のようなものがある。
タブ文字 ('\t') を 「\t」という2文字、改行を「'$' + 改行」の2文字として置き換えながら出力する cat コマンドを書きなさい。
次のようなコードを書いてみた。
// P.132 の練習問題
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 2048
static void do_cat(FILE *f);
int main(int argc, char *argv[]) {
if (argc == 1) {
do_cat(stdin);
} else {
for (int i = 1; i < argc; i++) {
FILE *f;
f = fopen(argv[i], "r");
if (!f) {
perror(argv[i]);
exit(1);
}
do_cat(f);
fclose(f);
}
}
exit(0);
}
static void do_cat(FILE *f) {
int c;
while ((c = fgetc(f)) != EOF) {
switch (c) {
case '\t':
putchar('\\');
putchar('t');
break;
case '\n':
putchar('$');
putchar(c);
break;
default:
putchar(c);
}
}
}
前回の疑似 cat と比較して新しい要素といえば FILE 型と、それに付随する fopen(3) と fclose(3) である。また putchar(3) も前回は使用していない。それぞれについて短く説明しよう。
まず fopen() はファイルパスからストリームを生成する関数である。返り値の型は FILE* になる。fclose() は逆にストリームを閉じる関数だ。fclose() を忘れないようにするため、fopen() と fclose() は同じ関数内で実行することが望ましい。
また FILE 型は typedef で定義されており、ファイルディスクリプタと、システムコールを実行する頻度を減らすための I/O のバッファリング機能を持つ。
なお putchar(c) は putc(c, stdout) と同じ意味である。出力先のストリームが stdout であるなら短く書けるというだけの話だ。
公式解答
サポートページからたどれる解答例を眺めてみると、僕のコードとそれほど違いがないように思える。
僕が putchar() を2回コールしていたのに対して、fputs("...", stdout) している点と、この関数コールが EOF を返すときに exit() するところが主な差分である。
本書の中に fgets(3) は第3引数のストリームから 1行読んで 第1引数のバッファ (char *buf) に格納するが fputs(3) は必ずしも「行」を出力するとは限らない、とある。したがって fputs("\\t", stdout) は改行を出力しない。なるほど。