exec 3>&1 てなんやねん！？

# はじめに
シェルスクリプトを眺めているとたまに出てくる表記。何かをリダイレクトしているというのは分かるけど、一念発起して細かく動作を調べてみた。

# 手順

## 端末を２つ開く
一つ目の端末は、以下のようにcdしておく。

```bash:端末A
$ cd /proc/$$/fd
```

## fdを表示する

```bash:端末A
$ 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をバックアップしておいて後で戻すという意図がある。

```bash:端末A
$ 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にはコマンドの出力がされなくなる。

```bash:端末A
$ exec 1>/tmp/redirect1.out
$ ls -l
$ # ls の結果は表示されない
```

## 端末Bでファイルをtailする

端末Aの出力がファイルにリダイレクトされている。

```bash:端末B
$ 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で確認する。

```bash:端末A
# あとで見やすいようにプロンプトを変えておく
$ PS1='(tail)$ '

(tail)$ exec 2>&1

# 何も出力されなくなるが、以下を入力する
ls -l
```

## 端末Bで確認する

端末Aの出力がファイルにリダイレクトされている。

```bash:端末B
(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を飛ばしことに深い意味はない。

```bash:端末A
# エコーバックも含めてファイルにリダイレクトされているため、表示は一切されない
exec 6>/tmp/redirect6.out
ls -l
```

`ls -l`の結果はファイルにリダイレクトされている。

```bash:端末B
(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へ出力する。

```bash:端末A
# 何も出力されないが、以下を入力する
echo "test1"
echo "test2" >&2
```

fd1, fd2がファイルへリダイレクトされていることを確認する。

```bash:端末B
(tail)$ echo "test1"
test1
(tail)$ echo "test2" >&2
test2
(tail)$
```

## fd6を使用する

fd6へ出力する。

```bash:端末A
# 何も出力されないが、以下を入力する
echo "test3" >&6
```

fd6がファイルへリダイレクトされていることを確認する。


```bash:端末B
(tail) $ echo "test3" >&6

# Ctrl-Cでtailを終了する
(tail) $ ^C

# 以下を入力する
$ cat /tmp/redirect6.out
test3
```

## fd1, fd2を元に戻す

バックアップしておいたfd3を複製することで元に戻せる。


```bash:端末A
# 何も出力されないが、以下を入力する
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を閉じる

これで完全に初期状態に戻る。

```bash:端末A
$ 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以上から自動的に採番してくれるらしい。

```bash
$ 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
```

# 感想

ずっとモヤモヤしていたのがすっきりしたけど、自分でシェルスクリプトを作る範囲内では使うことはなさそう。他の人が読めるとは限らないし。