手抜きプログラム(?!)の基礎中の基礎、 標準入出力の使い方や使うメリットを紹介します。 Unix系を前提にしますが、MS-Windows系のDOS窓でも大部分通用します。
フィルタが何か説明できる人にとっては、 常識しか書いてありませんので悪しからず。 (最後のヒヤドキュメントぐらいは価値があるかも。)
フィルタとは
標準入出力を用いるプログラムです。 有名なところでは、
'cat'
,'sort'
,'gzip'
などなど、 日頃からお世話になっているコマンドの多くはフィルタなのです。標準入出力とは
shell からプログラムを起動すると、 「入力・出力・エラー出力」のために3つのファイルが準備されます。 これが標準**です。 せっかくはじめから用意してくれるのだから、 プログラマがこれを使わない手はありませんね。
普通の接続先 C C++ Java Fortran file
へのリダイレクト標準入力 キーボード stdin
cin
System.in
5 command < file
標準出力 画面 stdout
cout
System.out
6 command > file
(上書き)command >> file
(付け足し)標準エラー出力 画面 stderr
cerr/clog
System.err
多くは0 (sh/bash) command 2> file
(csh/tcsh)command >& file
特に何も指定しなければ、標準入力はキーボードですし、 標準出力と標準エラー出力は画面です。 なんと、キーボードや画面はファイルだったのです。
リダイレクト
プログラムを起動する時に、ちょっと書き足すだけで 標準**の接続先を、キーボードや画面からファイルに変更することができます。 この操作をリダイレクションとかリダイレクトすると言います。 具体的な操作は上の表を見て下さい。
さてさて、リダイレクトすると何が便利になるかと言いますと、 プログラムを作る手間が省けます。 出力結果をファイルに保存するために、 わざわざプログラム中でファイルをオープンする必要はないのです。 標準出力に出力するプログラムを作っておけば、 プログラムを使う人がファイルにリダイレクトすればよいのです。 (画面とファイルに同時出力する方法は後述します。) 入力についても然り。
プログラム中でファイルをオープンする時、 ちゃんとエラー処理してますか? エラー処理を手抜きしてるなら、 それは手を抜く場所を間違えています。 最初からオープンされている標準入出力を使えばいいのです。 エラー処理はOSがやってくれます。
標準入出力の使い方
では、標準入出力の使い方ですが、 C言語では、gets(), puts(), printf() などの関数が 標準入出力を扱います。いつもお世話になってますね。 特別なことは何もしないでいいのです。 明示的に書くなら、fで始まる関数を用いて fgets( stdin, ...), fprintf( stdout, ... ) などとなります。 標準エラー出力は fprintf( stderr, ... ) と書きます。 (ちなみにgets()とfscanf()は、悪名高いエラー処理の出来ない関数ですので、 それぞれfgets()とfgets()+sscanf()で置き換えましょう。)
Fortran では、ハンドル番号が予約されています。 READ(5, ..), WRITE(6, ..), とすればよいようです。 困ったのが標準エラー出力。 WRITE(0, ..) とする処理系が多いようですが、 そうでない処理系も存在するようです。 移植性を優先するなら使えない、ということになります。
いずれにしても、標準エラー出力は、 2つめの出力ファイルとして積極的に使うものではなく、 実行中の状態表示や、エラー/警告表示のためのものですので、 「ファイルにリダイレクトされない」つもりで使うのがよいでしょう。
パイプ
標準入出力を用いると、プログラムを作る側が楽になることは既に説明しました。 しかし、プログラムを使う側にもメリットはあるのです。
- パイプ処理で他のプログラムとの連係が簡単に行える。 例えば、ソートやデータ圧縮も簡単に行える。
- パイプ処理を行うと、 Unix系では中間ファイルがディスクに書き込まれなくなるので、 処理が早くなることが期待できる。
- 入出力ファイル名を固定することもできる。 シェルスクリプトやバッチファイルを1行書くだけで実現できる。
というわけで、パイプ処理を説明しましょう。
'command1 | command2'
とすると、'command1'
の標準出力が'command2'
の標準入力につながります。 2段階で書くと'command1 > tempfile'
'command2 < tempfile'
と同等ですが、パイプした方が簡単に書けますね。外部プログラムとの連係
キーワード「less, sort, tee, gzip, zcat」
パイプ処理の上手な利用法は、 外部プログラムといかに組み合わせるかにかかっています。 いくつか典型的な例を紹介します。
- 後戻りして見る
'command | less'
で見れば、 画面の外に流れて行ったものも、戻って見ることもできますし、 検索もできます。- ソート
'command | sort'
で行単位でソートされます。 ちなみに'sort +5'
とすると、 「1行の中の(空白で区切られた)6つめのフィールドでソート」 されます。詳しくは'man sort'
をどうぞ。- 表示しながらファイルに保存
'command | tee outputfile'
とすると、 画面表示しながら、同じ内容をファイルに書き込んでくれます。 これはなかなか便利ですよ。- データ圧縮
'gzip'
と組み合わせると入力/出力ファイルを圧縮できます。
入力データが冗長なら、 前もって'gzip inputfile'
として圧縮しておき、'gzip -cd inputfile.gz | command'
とします。'gzip -cd'
の代わりに'zcat'
としても同じことです。
出力ファイルを圧縮するなら、'command | gzip > outputfile.gz'
とします。賢い less だと、普通に"outputfile.gz"
を 見るだけで展開して内容表示してくれます。
データが冗長で、1/10 以下に圧縮されるような時には、 この使い方は非常に効果的です。標準入出力では力不足な時
標準入出力は簡単かつ便利なものだということは 理解していただけたと思いますが、もちろん万能というわけではありません。 標準入出力だけでは対処できないのは、次の通りです。
- 入力ファイルが2つ以上
- 出力ファイルが2つ以上
- 入力ファイルを先頭に戻って同じ内容をもう一度読み込む
ファイルの数が足りないなら、 足りない分だけを自前でファイルオープンするか、 あるいは全ファイルを自前でファイルオープンするか、 どちらが適当かは状況次第でしょう。
2つめの出力ファイルとして標準エラー出力を使うことは可能ではありますが、 前述した通り、標準エラー出力は 「ファイルにリダイレクトされない=画面に表示される」 ことが期待されているので、 2つめの出力ファイルとしては不適当です。
ファイルの指定がなければ標準入出力
キーワード「FILE*型」
ファイルの指定があればそのファイルを使う、 指定がなければ標準入出力を使うという、 ちょっと凝ったプログラムもたくさんあります。 これを実現するC言語での典型的な処理例を書いておきます。
stdin/stdout
がFILE*
型であることが本質です。
#include <stdio.h> #include <stdlib.h> .... char *filename; /* NULL ならファイル指定なしとします */ FILE *fp; .... if ( filename == NULL ) { /* ファイル指定なし */ fp = stdin; } else { /* ファイル指定あり */ fp = fopen( filename, "r" ); if ( fp == NULL ) { fprintf( stderr, "'%s'が読み込めません.\n", filename ); exit(1); /* 異常終了 */ } } ....
標準入力の内容をスクリプトに書く
キーワード「ヒヤドキュメント」
Unix でしか通用しませんが、shやperlでは、 標準入力の接続先をキーボードでもファイルでもなく、 スクリプト内に書いておけます。 ヒヤドキュメントという機能ですが、
'command <<区切り単語'
〜'区切り単語'
と書きます。 詳しくは'man sh'
で"Here Documents"
を検索してみて下さい。 ここでは簡単なシェルスクリプトを紹介しておきます。
#!/bin/sh cat <<EOF This is test. EOF sort <<this-is-end EOF 345 321 123 678 this-is-end
実行結果は次のようになります。
This is test. 123 321 345 678 EOF
最後に
もともとOSの持つ機能を使って、簡単にプログラムを作ると楽しいですね。 しかも、他の人にも使いやすいプログラムが作れます。 もっと楽な方法があれば、うーん、教えて下さい。