Cプログラミング入門

第19回Chapter7 フィルタコマンドを作ろう

フィルタコマンドとは

ファイルの内容を読み込んで一定の処理を行い、結果をファイルに出力するようなコマンドを作成する場合、コマンド引数などで入出力のファイル名を指定するのではなく、標準入力から入力し、標準出力に出力するようにしておくと便利です。このようなコマンドをフィルタコマンドと言います。UNIXにはcat、sort、uniqなど、多くのフィルタコマンドが標準で用意されています。

フィルタコマンドは、コマンド自体のプログラムを変更することなく、その入出力をファイル、画面、キーボード、パイプなどに切り替えて使用することができます。

標準入力は、デフォルトではキーボード入力になっていますが、シェルのコマンドラインで< filenameと入力することにより、filenameという名前のファイルを読み込むことができます。同様に、標準出力は、デフォルトでは画面出力になっていますが、シェルのコマンドラインで> filenameと入力することにより、filenameという名前のファイルに書き込むことができます。また、複数のフィルタコマンドをパイプでつないで、command1 ¦ command2のようにすることもできます。この場合、command1の標準出力から出力されたデータが、パイプを通してそのままcommand2の標準入力になります。

簡易catコマンドを作ろう

データの加工は行わず、単に標準入力から読み込んだデータをそのまま標準出力に出力するだけのコマンドもフィルタコマンドです。UNIXのcatコマンドはまさにそのような動作をします。

実際のUNIXのcatコマンドは、引数で指定した複数のファイルを連結したり、オプション指定によりファイルの加工を行ったりすることもできますが、ここではそれらの機能はすべて省略し、単に標準入力から入力して標準出力に出力するだけの簡易catコマンドを作成することにします。

getchar()とputchar()でループ

簡易catコマンドを作るには、標準入力から1文字入力する関数と、標準出力に1文字出力する関数が必要です。それには次の2つの関数を使います。

c = getchar() ← 1文字入力(入力された文字がcに入る)
putchar(c) ← 1文字出力(cの内容が出力される⁠

※ getchar()、putchar()は実際にはstdio.hで定義されたマクロ関数ですが、ここではそこまでは踏み込まず、通常の関数であると考えることにします。

whileでEOFを判定

catコマンドのプログラムとしては、getchar()で読み込んだ文字をそのままputchar()で出力しながらループすればよいわけですが、ここで何らかの方法で入力が終了したかどうかかを判定し、入力が終了した場合はループを抜けるようにしなければなりません。

getchar()関数は、その入力が終了した場合は、EOF(End Of File)という特殊な文字が帰ってくるようになっています。そこで、while文を使って、getchar()の値がEOFではないという条件でループを継続すればよいことになります。なお、getchar()で入力される文字の文字コードは0~255(0~0xff)の範囲であり、EOFはこの範囲外の数値として、実際には(-1)として定義されています。

具体的なプログラムをリスト7.1に示します。

リスト中の、

while ((c = getchar()) != EOF)

という書き方がやや複雑です。ここでは、getchar()で読み込んだ文字を変数cに代入したあと、この部分の式の値(つまりcの値)「!=」非等号演算子によってEOFと比較しています。cの値がEOFでない場合はこの式が真になり、while文が継続されます。ここで、⁠=」の代入演算子の優先順位が「!=」の演算子よりも低いため、⁠=」の方を優先させるために「(c = getchar())」の部分を( )で囲む必要があることに注意してください。

リスト7.1 mycat.c
#include <stdio.h>

int
main()
{
  int c;

  while ((c = getchar()) != EOF) {
    putchar(c);
  }
  return 0;
}

mycat.cのwhileの部分を、次のリスト7.2のmycat_1.cように書き換えることもできます。mycat_1.cの方がプログラムの行数が増えてしまいますが、プログラムの動作の理解のための参考としてください。

リスト7.2 mycat_1.c(whileの部分を複数行に分けて書いた場合)
#include <stdio.h>

int
main()
{
  int c;

  for (;;) {
    c = getchar();
    if (c == EOF) {
      break;
    }
    putchar(c);
  }
  return 0;
}

mycat.cの実行例を図7.1に示します。このように、キーボードから入力した文字がそのまま表示されていることがわかります。[Ctrl]+[D]を押すと入力が終了となり、これでEOFが送られてmycatコマンドが終了します。

図7.1 リスト7.1(mycat.c)の実行例(キーボードから入力)
$ gcc -O2 -o mycat mycat.c
$ ./mycat
test input    ← キーボードから入力
test input    ← 入力した文字がそのまま表示される
[Ctrl]+[D]    ← [Ctrl]+[D]で終了
$

mycatコマンドの実行時に、標準入力をリダイレクトする<記号とともにファイルを指定すると、そのファイルの内容を表示することができます。図7.2の実行例は、ファイルとしてソースファイルのmycat.c自身を指定しているため、このmycat.cのファイルの内容が表示されます。なお、mycat.cの代わりに誤って実行バイナリファイルのmycatの方を表示しようとすると、画面にでたらめな文字列が表示されるだけでなく、画面(端末エミュレータのウィンドウ)自体が固まって操作不能になることがあるため、注意してください。

図7.2 リスト7.1(mycat.c)の実行例(ファイルから入力)
$ ./mycat < mycat.c
#include <stdio.h>

int
main()
… 省略 …
$

小文字を大文字に変換するフィルタ

簡易catコマンドができたら、これに簡単なデータ変換として、小文字を大文字に変換する処理を追加してみましょうリスト7.3⁠。

漢字コードをシフトJISからEUCに変換するフィルタコマンドなども、同様の方法で作成することができます。

リスト7.3 l2u.c
#include <stdio.h>

int
main()
{
  int c;

  while ((c = getchar()) != EOF) {
    if ('a' <= c && c <= 'z') {
      c += 'A' - 'a';
    }
    putchar(c);
  }
  return 0;
}
図7.3 リスト7.3(l2u.c)の実行例
$ gcc -O2 -o i2u i2u.c
$ ./l2u
hello        ← キーボードから入力
HELLO        ← 入力した文字が大文字に変換されて表示される
[Cntl]+[D]   ← [Ctrl]+[D]で終了
$

l2u.cでは小文字を大文字に変換することを自分でプログラムしましたが、libc(標準ライブラリ関数)には小文字を大文字に変換するためのtoupper()という関数が用意されており、こちらを使うという方法もあります。

このl2u.cを少し修正すれば、⁠大文字なら小文字、小文字なら大文字に変換するフィルタコマンド」を作成することもできます。そのほか、UNIXのwcコマンドやheadコマンドなどの簡易バージョンを作ってみることもできるでしょう。

おすすめ記事

記事・ニュース一覧