「1>/dev/null 2>&1と2>&1 1>/dev/nullの違い」へのご指摘の調査

## 前回の記事へのご指摘
[前回の記事](http://qiita.com/hasegit/items/13d03dd5353c2031cf91)を執筆したところ、ご指摘を頂けたのですが、
私の理解が追いついていないところがありました。それが以下です。

a. どこまで行っても 1 は標準出力と同義 ( 同様に 2 は標準エラーと同義 ) 
b. 子は親のファイルディスクリプタを引き継ぐ
c. リダイレクトを指定すると、親のものを引き継がずに切り替わりが起こる
d. 子は親の標準出力と同じである必要はない

では、順にみていきます。

## a.のご指摘の調査
ファイルディスクリプタ0は標準入力、1を標準出力、2を標準エラー出力と呼ばれます。
その関係性が崩れることはありません。そのことは、Linux上のファイルを見る事でも窺い知れます。

```
# ls -l /dev/std*
lrwxrwxrwx. 1 root root 15 11月  6 11:32 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx. 1 root root 15 11月  6 11:32 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx. 1 root root 15 11月  6 11:32 /dev/stdout -> /proc/self/fd/1
```

この通り、stdinはfd0、stdoutはfd1、stderrはfd2を指し示しています。
では、fdの0から2は何を指しているのでしょうか。

```
# ls -l /proc/self/fd/{0..2}
lrwx------. 1 root root 64  3月  2 22:30 /proc/self/fd/0 -> /dev/pts/1
lrwx------. 1 root root 64  3月  2 22:30 /proc/self/fd/1 -> /dev/pts/1
lrwx------. 1 root root 64  3月  2 22:30 /proc/self/fd/2 -> /dev/pts/1
```

/dev/pts/1ですので、疑似端末(sshした時などの接続先端末)となります。
そして、このfdの向き先を最終的に変えるのがリダイレクトとなります。

私は**1の先に標準出力があるもの**だと思っていたのですが、
正しくは**1こそが標準出力と呼ばれるもの**、そのものだったということですね。

以上より、

>a. どこまで行っても 1 は標準出力と同義 ( 同様に 2 は標準エラーと同義 ) 

については、ご指摘の通りとなります。

## b.のご指摘の調査

>b. 子は親のファイルディスクリプタを引き継ぐ

これはforkを調べたら分かりました。

```
The child inherits copies of the parent's set of open file descriptors.
```

ただ、そのものを引き継ぐわけではなく、コピーを引き継ぐようです。
つまり、プロセス毎にファイルディスクリプタ自体は別ですが、指し示す先は同じです。

## c.のご指摘の調査

> c. リダイレクトを指定すると、親のものを引き継がずに切り替わりが起こる

これについては、straceを使って、どのような処理が
リダイレクト時に内部的に行われているのかを見て判断します。

では、前回と同じコマンド(正しいほう)でトレースしてみます。

```
strace -o redirect.txt -ff bash -c "ls dada mama 1>/dev/null 2>&1"
```

一応straceのオプションについて説明します。
-oはトレース結果をファイル出力します。
-ffは、子プロセスまでトレースし、プロセス毎にファイルを分けて出力するオプションです。

尚、bashにコマンドをいったん嚙ませないと、うまくリダイレクト結果が取れませんでした。

見ていきます。

```
# ls
dada  redirect.txt.817  redirect.txt.818
# grep "/dev/null" redirect.txt.817
execve("/bin/bash", ["bash", "-c", "ls dada mama 1>/dev/null 2>&1"], [/* 18 v
ars */]) = 0
```

なぜかプロセスが二つ生成されてました。
どうやら、817(親プロセス側)ではリダイレクトらしき事は行われていないようです。
これは、リダイレクトを親プロセス側で実施してしまうと、その後の親プロセスの処理も
リダイレクトの影響を意図せず受けてしまうからかな、と思います。
では、818側を見てみます。

```
# grep "/dev/null" redirect.txt.818
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
```

/dev/nullがopenされています。
openはファイルを開き、オープンしたファイルの
ファイルディスクリプタを返すシステムコールです。
今回は、/dev/nullが3でopenされているようです。
周辺のログを見てみましょう。

```
# grep -A 6 "/dev/null" redirect.txt.818
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
dup2(3, 1)                              = 1
close(3)                                = 0
dup2(1, 2)                              = 2
fcntl(1, F_GETFD)                       = 0
execve("/bin/ls", ["ls", "dada", "mama"], [/* 17 vars */]) = 0
brk(0)                                  = 0x1014000
```

リダイレクトを行っている部分が見えましたね。
リダイレクトの正体は**dup2**です。
dup2は、二つ目の引数のファイルディスクリプタを
一つ目の引数のファイルディスクリプタの複製として作る、
というシステムコールです。

一連の流れをまとめると、

1. プロセスをcloneする(子プロセスを作る) 
2. /dev/nullをファイルディスクリプタ3で開く
3. 1を3(/dev/null)の複製として作成
4. 3をクローズ
5. 2を1(/dev/null)の複製として作成
6. 2にエラーメッセージを、1にls結果を出力(いずれも/dev/nullに対して出力)

ということをしています。

従いまして、リダイレクトした際、親のものを引き継がずに切り替わりが起こるわけではなく、
親のファイルディスクリプタを引き継いだ上で、dup2によってファイルディスクリプタの
複製を行い、指し示す先を切り替えています。

ついでなので、だめなほう(2>&1 1>/dev/null)の動きもみてみます。

```
# strace -o redirect2.txt -ff bash -c "ls dada mama 2>&1 1>
/dev/null"
ls: mama にアクセスできません: そのようなファイルやディレクトリはありません
# ls
dada  redirect2.txt.3820  redirect2.txt.3821
```

やはり、プロセスは二つのようです。中見ます。

```
dup2(1, 2)                              = 2
fcntl(1, F_GETFD)                       = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
dup2(3, 1)                              = 1
close(3)                                = 0
execve("/bin/ls", ["ls", "dada", "mama"], [/* 17 vars */]) = 0
brk(0)                                  = 0xa8e000
```

先ほどと逆になっただけですね。

1. プロセスをcloneする(子プロセスを作る) 
2. 2を1の複製として作成(先は一緒なので意味なし)
3. /dev/nullをファイルディスクリプタ3で開く
4. 1を3(/dev/null)の複製として作成
5. 3をクローズ
6. 2にエラーメッセージを、1(/dev/null)にls結果を出力

となります。

## d.のご指摘の調査

>d. 子は親の標準出力と同じである必要はない

cの検証の通り、切り替わった状態で子供が生まれるわけでないので、
恐らくこれ以外の言い回しになると思います。

## 〆

**自分の無知さを痛感しました。**
それなりに時間をかけて調べたつもりだったのに、まだまだでしたね…。
[angel_p_57](http://qiita.com/angel_p_57)さん、本当にありがとうございました。

取りあえず、前記事は黒歴史として残しておいて、別途ちゃんとしたやつを書いて、
そちらに誘導するようにします。