シェルでの実装場所が分からなかったので、straceを使ってシステムコールレベルでの挙動を確認してみました。
使用するシェルによって細かい部分は異なるが、大まかな流れということで。
標準出力リダイレクト
まずは、以下のようなechoで文字列をファイルに書き込む場合を確認する。
$ strace sh -c "echo hoge > sample.txt"
(省略)
open("sample.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "hoge\n", 5) = 3
dup2(10, 1) = 1
close(10) = 0
上記処理を順に追っていくと、
1 sample.txtをopen
し、3が割り当てられる。
2 fcntl
にて10以降の空きのファイルディスクリプタ、今回は10に標準出力のファイルディスクリプタが複製される。
3 標準出力のファイルディスクリプタは10に複製されたのでclose
する。
4 sample.txtのファイルディスクリプタをdup2
で1に複製することで、標準出力への書き込みがsample.txtに書き込まれる。
5 ファイルへの書き込みは1を通じて行われるので3はclose
する。
6 通常はwrite(1,...)
とすることで、標準出力に結果が表示されるが、1はsample.txtとなっているため結果はファイルに書き込まれる。
7 標準出力のファイルディスクリプタを元に戻すためdup2
で10を1に複製する
8 複製された標準出力のファイルディスクリプタは不要のため10をclose
する。
以上のように標準出力のファイルディスクリプタを退避させ、1に書き込み対象のファイルディスクリプタを割り当てることで標準出力をファイルへ書き込むことを実現しているようです。
ちなみに、>>
でファイルに上書きする場合は、openフラグのO_TRUNC
がO_APPEND
となり、ファイル先頭から上書きでなく、末尾から追記となる。
標準出力と標準エラー出力を同時にリダイレクト
2>&1
を使って標準出力と標準エラー出力の両方出力する場合も確認してみます。
$ strace -f sh -c "ls foo > err.txt 2>&1"
(省略)
open("err.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
fcntl(2, F_DUPFD, 10) = 11
close(2) = 0
fcntl(11, F_SETFD, FD_CLOEXEC) = 0
dup2(1, 2) = 2
(省略)
[pid 5851] write(2, "ls: ", 4) = 4
[pid 5851] write(2, "cannot access foo", 17) = 17
[pid 5851] write(2, ": No such file or directory", 27) = 27
[pid 5851] write(2, "\n", 1) = 1
[pid 5851] close(1) = 0
[pid 5851] close(2) = 0
(省略)
dup2(10, 1) = 1
close(10) = 0
dup2(11, 2) = 2
close(11) = 0
上記の場合も標準出力1と標準エラー出力2はそれぞれ10と11に退避させ、1と2が書き込み対象ファイルに割り当てられます。