はじめに
シェルスクリプトを眺めているとたまに出てくる表記。何かをリダイレクトしているというのは分かるけど、一念発起して細かく動作を調べてみた。
手順
端末を2つ開く
一つ目の端末は、以下のようにcdしておく。
$ cd /proc/$$/fd
fdを表示する
$ ls -l
total 0
lrwx------ 1 user group 64 Feb 8 16:51 0 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 16:51 1 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 16:51 2 -> /dev/pts/1
fd3をopenする
fd1をfd3へコピーする。これは、fd1をバックアップしておいて後で戻すという意図がある。
$ exec 3>&1
$ ls -l
total 0
lrwx------ 1 user group 64 Feb 8 12:18 0 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 12:18 1 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 12:18 2 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 12:18 3 -> /dev/pts/1
fd1をファイルにリダイレクトする
この瞬間から、端末Aにはコマンドの出力がされなくなる。
$ exec 1>/tmp/redirect1.out
$ ls -l
$ # ls の結果は表示されない
端末Bでファイルをtailする
端末Aの出力がファイルにリダイレクトされている。
$ tail -f /tmp/redirect1.out
# ここから下はリダイレクトされたもの
total 0
lrwx------ 1 user group 64 Feb 8 14:56 0 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 14:56 1 -> /tmp/redirect1.out
lrwx------ 1 user group 64 Feb 8 14:56 2 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 15:00 3 -> /dev/pts/1
fd2にfd1を複製する
最初に exec 1>/tmp/redirect1.out 2>&1
とすることと結果的に同じ。また、ここから端末Aには一切出力されなくなるので、端末Bで確認する。
# あとで見やすいようにプロンプトを変えておく
$ PS1='(tail)$ '
(tail)$ exec 2>&1
# 何も出力されなくなるが、以下を入力する
ls -l
端末Bで確認する
端末Aの出力がファイルにリダイレクトされている。
(tail)$ ls -l
total 0
lrwx------ 1 user group 64 Feb 8 16:05 0 -> /dev/pts/1
l-wx------ 1 user group 64 Feb 8 16:05 1 -> /tmp/redirect1.out
l-wx------ 1 user group 64 Feb 8 16:05 2 -> /tmp/redirect1.out
lrwx------ 1 user group 64 Feb 8 16:05 3 -> /dev/pts/1
fd6をopenする
新たにfd6をopenし、ファイルにリダイレクトする。fd4, fd5を飛ばしことに深い意味はない。
# エコーバックも含めてファイルにリダイレクトされているため、表示は一切されない
exec 6>/tmp/redirect6.out
ls -l
ls -l
の結果はファイルにリダイレクトされている。
(tail)$ ls -l
total 0
lrwx------ 1 user group 64 Feb 8 15:01 0 -> /dev/pts/1
l-wx------ 1 user group 64 Feb 8 15:01 1 -> /tmp/redirect1.out
l-wx------ 1 user group 64 Feb 8 15:01 2 -> /tmp/redirect1.out
lrwx------ 1 user group 64 Feb 8 15:02 3 -> /dev/pts/1
l-wx------ 1 user group 64 Feb 8 15:15 6 -> /tmp/redirect6.out
fd1, fd2を使用する
fd1, fd2へ出力する。
# 何も出力されないが、以下を入力する
echo "test1"
echo "test2" >&2
fd1, fd2がファイルへリダイレクトされていることを確認する。
(tail)$ echo "test1"
test1
(tail)$ echo "test2" >&2
test2
(tail)$
fd6を使用する
fd6へ出力する。
# 何も出力されないが、以下を入力する
echo "test3" >&6
fd6がファイルへリダイレクトされていることを確認する。
(tail) $ echo "test3" >&6
# Ctrl-Cでtailを終了する
(tail) $ ^C
# 以下を入力する
$ cat /tmp/redirect6.out
test3
fd1, fd2を元に戻す
バックアップしておいたfd3を複製することで元に戻せる。
# 何も出力されないが、以下を入力する
exec 1>&3
exec 2>&3
# プロンプトを適当に直す
(tail)$ PS1='$ '
# fd1, fd2 が元に戻っている
$ ls -l
total 0
lrwx------ 1 user group 64 Feb 8 15:01 0 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 15:01 1 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 15:01 2 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 15:02 3 -> /dev/pts/1
l-wx------ 1 user group 64 Feb 8 15:15 6 -> /tmp/redirect6.out
不要になったfd3, fd6を閉じる
これで完全に初期状態に戻る。
$ exec 3>&-
$ exec 6>&-
$ ls -l
lrwx------ 1 user group 64 Feb 8 12:29 0 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 12:29 1 -> /dev/pts/1
lrwx------ 1 user group 64 Feb 8 12:29 2 -> /dev/pts/1
おまけ
最近のbashは、{varname} という形式を使えば、fdの番号を10以上から自動的に採番してくれるらしい。
$ exec {redirect1}>&1
$ exec {redirect2}>&1
$ ls -l
total 0
lrwx------ 1 user group 64 Feb 8 16:29 0 -> /dev/pts/3
lrwx------ 1 user group 64 Feb 8 16:29 1 -> /dev/pts/3
lrwx------ 1 user group 64 Feb 8 16:30 10 -> /dev/pts/3
lrwx------ 1 user group 64 Feb 8 16:30 11 -> /dev/pts/3
lrwx------ 1 user group 64 Feb 8 16:29 2 -> /dev/pts/3
$ exec {redirect1}>&-
$ exec {redirect2}>&-
$ ls -l
total 0
lrwx------ 1 user group 64 Feb 8 16:29 0 -> /dev/pts/3
lrwx------ 1 user group 64 Feb 8 16:29 1 -> /dev/pts/3
lrwx------ 1 user group 64 Feb 8 16:29 2 -> /dev/pts/3
感想
ずっとモヤモヤしていたのがすっきりしたけど、自分でシェルスクリプトを作る範囲内では使うことはなさそう。他の人が読めるとは限らないし。