bashのリダイレクトの話

# はじめに
先日、[>/dev/null 2>&1は順序が逆だとダメ](http://qiita.com/ryskiwt/items/d4e7846fd47364a2a4e2)という投稿をしましたが、内容がダメダメだったので、再度まとめ直しました。

# ざっくりした理解
## ファイルディスクリプタ
入出力インターフェースを識別する識別番号。デフォルトでは下記の通りの設定となっている。

|番号|インターフェース|
|:-:|:-:|
|0|標準入力|
|1|標準出力|
|2|標準エラー出力|

## 出力リダイレクト
コマンドの実結果をファイル`file`に書き出す。`n`により、出力インターフェースを指定する。（n=1は標準出力、n=2は標準エラー出力）


```bash
cmd [n]>file
```

## 入力リダイレクト
コマンドへ渡す入力をファイル`file`から読み込む。`n`により、入力インターフェースを指定する。（n=1は標準入力）

```bash
cmd [n]<file
```

## 出力を別の出力へリダイレクトする
`n`の出力先を`m`と同じに設定する。

```bash
cmd [n]>&[m]
```

# ちゃんと調べた結果
## ファイルディスクリプタ（[IT用語辞典](http://e-words.jp/w/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%87%E3%82%A3%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%82%BF.html)より）
>ファイルディスクリプタとは、プログラムがアクセスするファイルや標準入出力などをOSが識別するために用いる識別子。0から順番に整数の値が割り当てられる。OSによってはファイルディスクリプタにバッファ管理機能なども含めた「ファイルハンドル」と呼ばれる管理体系が存在する。

>ファイルディスクリプタには、識別子とともにファイル名、ファイルサイズ、プログラムが操作中のファイル内の位置、ファイル作成、更新日時などの情報が含まれており、OSは識別子によってどのファイルを操作するかを判断する。

>通常、0：標準入力（stdin）、1：標準出力（stdout）、2：標準エラー出力（stderr）の3つはOS（シェル）が最初に用意するため、プログラムがファイルをオープンすると「3」から順番にディスクリプタが割り当てられる。

プログラム（プロセス）がアクセスする標準入出力やファイルに関連する情報を保持した識別子。保持する情報には、入出力先のファイルパスが含まれる。それぞれの識別子に整数値の識別番号が割り振られる。（ただの識別番号ではなく、ファイルディスクリプタという識別子オブジェクトに識別番号がついているイメージ）

## リダイレクト（ちゃんと`man bash`を読んでみる）
プロセスの各入出力に対応するファイルディスクリプタを新規作成/複製することにより、入出力先を変更すること。プロセスが生成された時点では、標準入力に対応するファイルディスクリプタが`0`に、標準出力に対応するファイルディスクリプタが`1`、標準エラー出力に対応するファイルディスクリプタが`2`となっている。また、`3`以上の番号にファイルディスクリプタを設定することもできる。

### 入出力リダイレクト
ファイル`word`を入力元/出力先とする新規の入力ファイルディスクリプタを作成し、識別番号`n`をつける。

```bash
cmd [n]>word # Redirecting Output
cmd [n]<word # Redirecting Input
```

### ファイルディスクリプタの複製
識別番号`m`のファイルディスクリプタを複製し、識別番号`n`をつける。

```bash
cmd [n]>&[m] # Duplicating Output File Descriptor
cmd [n]<&[m] # Duplicating Input File Descriptor
```

#### 【注意】
[標準エラーのリダイレクト](http://wiki.bit-hive.com/north/pg/%C9%B8%BD%E0%A5%A8%A5%E9%A1%BC%A4%CE%A5%EA%A5%C0%A5%A4%A5%EC%A5%AF%A5%C8)によると、複製するといいつつ、内部処理としては、`n`を示すポインタに`m`のポインタを設定している処理のようです。

### 特別なファイルパス
上記の`word`には、ファイルのパスだけでなく、下記の特別なパスが使用できる。

|パス|内容|
|:-:|:-:|
|/dev/null|虚無（ここへ向けてリダイレクトすると出力されない）|
|/dev/fd/n|識別番号`n`で表されるファイルディスクリプタ|
|/dev/stdin|標準入力|
|/dev/stdout|標準出力|
|/dev/stderr|標準エラー出力|


# `cmd /dev/null 2>&1`の順序問題（再び）
`>`はファイルディスクリプタの新規作成、`>&`はファイルディスクリプタの複製、として再度`cmd /dev/null 2>&1`の順序問題を確認する。

元の設定

```
+-----+
|    1|-----/dev/stdout
|     |
|    2|-----/dev/stderr
+-----+
```

## 【誤】cmd 2>&1 >/dev/null
`2>&1`で、`2`を`1`に複製

```
+-----+
|    1|-----/dev/stdout
|     |       |
|    2|-------+  (/dev/stderr)
+-----+
```
`>/dev/null`で、新規に`1`へ`/dev/null`を設定

```
         +--/dev/null
+-----+  |
|    1|--+  /dev/stdout
|     |       |
|    2|-------+  (/dev/stderr)
+-----+
```

## 【正】cmd >/dev/null 2>&1
`>/dev/null`で、新規に`1`へ`/dev/null`を設定

```
         +--/dev/null
+-----+  |
|    1|--+  (/dev/stdout)
|     |
|    2|-----/dev/stderr
+-----+
```
`2>&1`で、`2`を`1`に複製

```
         +--/dev/null
+-----+  |    |
|    1|--+    |  (/dev/stdout)
|     |       |
|    2|-------+  (/dev/stderr)
+-----+
```

# `>a.txt 2>&1`と`>a.txt 2>a.txt`は違う問題
こちらも、再度確認する。

元の設定

```
+-----+
|    1|-----/dev/stdout
|     |
|    2|-----/dev/stderr
+-----+
```

## cmd >a.txt 2>&1
`>a.txt`で、新規に`1`へ`a.txt`を設定

```
         +--./a.txt
+-----+  |
|    1|--+  (/dev/stdout)
|     |
|    2|-----/dev/stderr
+-----+
```
`2>&1`で、`2`を`1`に複製

```
         +--./a.txt
+-----+  |    |
|    1|--+    |  (/dev/stdout)
|     |       |
|    2|-------+  (/dev/stderr)
+-----+
```

## cmd >a.txt 2>a.txt
`>a.txt`で、新規に`1`へ`a.txt`を設定

```
         +--./a.txt
+-----+  |
|    1|--+  (/dev/stdout)
|     |
|    2|-----/dev/stderr
+-----+
```
`2>a.txt`で、新規に`2`へ`a.txt`を設定（別々に新規のファイルディスクリプタを生成するため、お互いに同じファイルに上書き合ってしまう）

```
         +--./a.txt
+-----+  |
|    1|--+  (/dev/stdout)
|     |
|    2|--+  (/dev/stderr)
+-----+  |
         +--./a.txt
```

# 参考
`man bash`によれば、bashでは下記の構文が利用できるらしい。これは`>word 2>&1`のsemantically equivalentだそうなので、bashの場合はこれを使うと間違いがなさそう。

```bash
cmd &>word
```

また、`>`や`>&`、`&>`以外にも、`>|`や`<>`など様々リダイレクトの種類があるらしい。
気になる方は、[あなたの知らない>|と<>の使い方](http://www.atmarkit.co.jp/ait/articles/1207/13/news144.html)などを参考に。