引き続き「ふつうの 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)
は改行を出力しない。なるほど。