パイプとファイルディスクリプタ — 縦棒1本が、何をつなぎ替えているのか
シェルで書く 縦棒(パイプ)は、左のコマンドの出力を右のコマンドの入力にするものだと説明されます。それは正しいのですが、実際に何がつなぎ替えられているのかまでは見えません。
答えはファイルディスクリプタという、プロセスが持つ番号つきの口です。縦棒1本が起こしているのは、この番号のつなぎ先を書き換えるという、ただそれだけの操作。図1を1ステップずつ進めて、その瞬間を見てください。
ファイルディスクリプタ — プロセスが持つ番号つきの口
プロセスが外とやり取りするとき、相手がファイルでも端末でもネットワークでも、すべて「番号」で扱います。この番号がファイルディスクリプタ(fd)です。生まれた時点で3つ用意されていて、0 が stdin(入口)、1 が stdout(出口)、2 が stderr(エラー用の出口)と決まっています。
重要なのは、プログラム側は番号しか知らないということです。cat は「fd 1 に書く」としか書かれていません。その先が画面なのか、ファイルなのか、パイプなのかをcat 自身は気にしていない。だから外側から自由につなぎ替えられます。
縦棒がやっているのは、つなぎ替えだけ
シェルが縦棒を見つけると、まずカーネルにパイプを1本作らせます。パイプは書き口と読み口が対になった、名前のない一方通行の土管です。
そのうえでシェルは、左のプロセスの fd 1 を書き口へ、右のプロセスの fd 0 を読み口へつなぎ替えてから、それぞれのコマンドを起動します。図1のステップ2で、両側の行が同時に書き換わるのがその瞬間です。cat も grep も、自分がつなぎ替えられたことを知りません。
左が書いた内容は土管に溜まり、右が読んだぶんだけ減ります。溜まりすぎれば左は自動的に待たされ、右が読み終わるまで進めません。この待ち合わせのおかげで、巨大なログでも全部をメモリに載せずに流し込めます。
stderrはパイプに乗らない
縦棒がつなぎ替えるのは fd 1 だけです。fd 2(stderr)は端末につながったまま。図1の最後のステップで、cat のエラーメッセージだけがパイプを迂回して画面に直行するのはこのためです。
パイプでつないだのにエラーだけが画面に出てくるのは、この仕様どおりの動きです。エラーも一緒に流したいときは 2>&1 と書きます。これは「fd 2 のつなぎ先を、fd 1 と同じところにしろ」という指示で、結局やっていることは図1と同じつなぎ替えです。
- ファイルディスクリプタ
- プロセスが入出力先を指すのに使う番号。0/1/2は最初から決まっている。
- stdin / stdout / stderr
- それぞれ fd 0 / 1 / 2。入口・出口・エラー用の出口。
- パイプ
- 書き口と読み口が対になった一方通行の通り道。縦棒を書くと作られる。
- つなぎ替え(dup2)
- ある番号の指す先を別のものに差し替える操作。リダイレクトの正体。
- 2>&1
- fd 2 のつなぎ先を fd 1 と同じにする指定。エラーもパイプに乗る。
まとめ
縦棒は魔法ではなく、「土管を1本作り、両側のfdをそこに向け直す」という手続きです。番号は最後まで 0・1・2 のまま変わりません。変わるのはその番号の先に何がつながっているかだけ。ここが腑に落ちると、リダイレクトも 2>&1 も、同じ1つの操作の言い換えだと分かります。