あしのあしあと

2011-04-26

絵で見てわかるファイルディスクリプタ・パイプ・リダイレクト

| 00:06 | 絵で見てわかるファイルディスクリプタ・パイプ・リダイレクトを含むブックマーク

シェルで1行ずつファイルから読み込む - あしのあしあと」に引き続き、「シェルで1行ずつファイルから読み込む」シリーズの2回目。今回は、ファイルディスクリプタ(ファイル記述子)、パイプ(パイプライン)、リダイレクト(リダイレクション)のおさらい。今さら感は満載だが、まぁよしとする。なお、本エントリで用いている図については、書籍「UNIXネットワークプログラミング入門」を参考に作成した(読んだのは7年前で、今手元に本がないのだが、確かこんな絵が書いてあったと思う)。


まず、簡単な凡例を。

f:id:higher_tomorrow:20110424195859p:image:w480


で、シェルやコマンドのプロセスが起動した時だが、次の絵のような状態になっている。

f:id:higher_tomorrow:20110424195746p:image:w480

プロセスには(すぐに利用可能な)0番、1番、2番のファイルディスクリプタが用意されていて、それぞれ画面と紐づいている(にょろっとした矢印、方向も大事)。0番を「標準入力」、1番を「標準出力」、2番を「標準エラー出力」と呼ぶ(のはいいか)。

よく使っているコマンドには、これら0番、1番、2番を使って処理を実行するものがけっこうある。例えば、echo コマンドは、引数で指定した値を「標準出力」に出力する。これは次の絵のようなイメージになる。

f:id:higher_tomorrow:20110424195943p:image:w480

まぁ簡単だ。


シェルでは多くのコマンドが、シェルとは別のプロセス*1で実行される。パイプは、あるプロセスの標準出力を別のプロセスの標準入力につなげる、プロセス間でデータを共有する方法の1つ。外部コマンドである sed は、(オプションによっては)標準入力を受け取って、処理結果を標準出力に出力する。なので、例えば“echo "abcdef" | sed 's/^abc//'”は、次の絵のようなイメージになる(一部、直接関係しない番号や矢印を省略した)。

f:id:higher_tomorrow:20110424200033p:image:w480

echo コマンドでファイルディスクリプタ1番から出力した文字列 abcdef を、sed コマンドを用いて(先頭の abc を)フィルタし、フィルタ後の文字列(この場合は def)を標準出力に出力している。


ファイルディスクリプタの向き先のファイルを変えるのが、リダイレクト。単純に“>”と記載すると、“1>”の意味になる(1が省略されているだけ)。“1> file.txt”と書くと、一時的に、ファイルディスクリプタ1番の向き先を、ファイル file.txt に変更することになる。もちろん、コマンドが終了したら、向き先は元の画面ファイルに戻る。

f:id:higher_tomorrow:20110424200103p:image:w480

ちなみに、“<”は“0<”が省略されているだけ。一時的に標準入力をファイルにすることができる。


さて、このエントリの最後に、複数の出力を集約する例をあげよう。よく使っている(だろう)スクリプト“echo "abcdef" > file.txt 2>&1”は、次の絵のようなイメージになる。

f:id:higher_tomorrow:20110424202520p:image:w480

きちんと、左から順に追っていけば、難しくない。まず、“> file.txt”なので、1番をファイル file.txt に向けた。で、“2>”なので、2番の向き先なのだが、“&1”とあるので、1番と同じファイルに向けることを意味している。今1番は、file.txt に向いている*2。これで、echo コマンドの1番も2番も、file.txt に出力されることになる。この例では、2番からは何も出力されないけどね。


これを踏まえて、次回は while、read、exec の使い方を説明する。で、ようやくファイルを1行ずつ処理できるようになる。

*1:まぁ、子プロセス。forkしてexec。

*2:順番を変えて、“echo "abcdef" 2>&1 > file.txt”とやると、2番を1番と同じ画面に向けて、その後で1番を file.txt に向けるので、2番からの出力は file.txt に出力されない。