Linux-リダイレクト「2>&1」について理解する（前編）

#はじめに
本記事では、Linuxのリダイレクト機能の中でもよく使うがイマイチ理解しにくい「2>&1」について、
その意味と使い方を解説していきたいと思います。


##注意事項
本記事では、なるべくわかりやすくなるように、できるだけ難しい言葉を避けて記述を行なっています。
言葉の正確さよりも理解しやすさの方を優先していますので、
一部用語の正確性を欠いた部分があるかもしれません。
あらかじめご了承ください。

なお、シェルはbashを利用しています。

##実行環境
実行環境は以下の通りです。

```
$ uname -rs
Linux 3.10.0-1160.31.1.el7.x86_64
$cat /etc/centos-release
CentOS Linux release 7.9.2009(Core)
```

##前提知識
本記事は以下の内容についてすでに理解がある方を対象としています。

・リダイレクト
・標準入力、標準出力、標準エラー出力
・ファイルディスクリプタ


#2>&1とはどういう意味なのか
これまで何となく2>&1というものを書いてきた方もおられるでしょう。
この記号ですが、どういう意味だかご存知でしょうか。
さまざま解釈はあるかと思いますが、おおよそ以下のような解釈になります。

**<font color="Red">ファイルディスクリプタ２番の出力先をファイルディスクリプタ１番を複製したものに付け替える</font>**

はい、意味不明ですよね...
でも、これからこれについて説明をしていくので安心してください。


##2>&1を分解して考えてみる
まずはこのへんてこりんな記述を分解して考えてみましょう。
それぞれが意味するものを分けて書くと、以下のようになります。

・2：ファイルディスクリプタ２番
・>：出力のリダイレクト
・&：ファイルディスクリプタを複製する
・1：ファイルディスクリプタ１番

これを図にしたものが以下です。
![5F2A45BF-852A-44A7-B631-AB2D3F8AD25F_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1731510/b649b463-3be4-edaa-a5b9-6d6538d6ab22.jpeg)



##2>&1とした時の挙動を確認してみる
記号ばかり眺めていてもわかりにくいので、もう少し挙動を可視化してみたいと思います。

普段、**プロセスのファイルディスクリプタ0、1、2番は、それぞれ、
標準入力、標準出力、標準エラー出力に繋がっています。**

図にするとこんな感じ。
![E06F1B76-3FEF-4373-A7F2-B154FEDDE310_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1731510/81ba9237-a26d-17f7-deee-1215901eb538.jpeg)
この図の状態から、2>&1を使用すると次のような挙動になります。

**<font color="Red">①ファイルディスクリプタ１番のコピーが作成される
②開かれているファイルディスクリプタ２番が閉じられる
③①でコピーされたものがファイルディスクリプタ２番となる</font>**

順番に図解していきます。

**①ファイルディスクリプタ１番のコピーが作成される**
![F6B68305-1AC2-4CBD-A7D5-61FCC9AC95C0_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1731510/219e045e-d147-b243-e9cc-6870b5c8b4c7.jpeg)


**②開かれているファイルディスクリプタ２番が閉じられる**
![0DF8C2C1-41CD-4441-AC5B-B410B65B4B8A_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1731510/6caf52f3-4563-4681-d105-a71ac1949377.jpeg)


**③①でコピーされたものがファイルディスクリプタ２番となる**
![E9A3E9AC-47DD-473B-9DDF-0E4135F0A05E.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1731510/ea272b89-5786-bd9b-af2f-f2bd88c843d5.jpeg)


これで完成です！
では、2>&1とする前と後ではどこが変わったでしょうか。
正解は「標準エラーの出力先が変わっている」です。

2>&1の使用前は、ファイルディスクリプタ２番を指定すると、/dev/tty1にデータが流されるようになっていました。
ですが、2>&1使用後は、/home/user1/test.logに流されるようになっています。
つまり、**ファイルディスクリプタ１番が複製され、ファイルディスクリプタ２番がそれに付け替わった**わけです。


#2>&1はどんな時に使うのか
挙動は何となく分かっていただけたかと思いますが、では実際にどのような時に使うのでしょうか。

Linuxでは、**どのプロセスもエラーが発生するとファイルディスクリプタ２番にエラーメッセージを流すという共通の挙動を取ります。**
つまり、エラー文言はすべてファイルディスクリプタ２番で指定されたファイルに出力されるのです。
（端末画面の入出力もデバイスファイルというファイルを介して行われます）

よって、2>&1を使うシーンとしては、

**<font color="Red">エラーメッセージの出力先を変更したい時。
かつ、その出力したい先がファイルディスクリプタ１番の出力先と同じ時。</font>**

となります。
それがどういうことか、2>&1を使った場合と使わなかった場合を比較してみましょう。

今、以下のような状況であるとします。

```
$ tty                   #端末画面を確認
/dev/tty1
$ ls -l /proc/$$/fd　　　#現在のシェルのファイルディスクリプタを確認
total0
lrwx------. 1 user1 user1 64 Jul 7 22:00 0 -> /dev/tty1
lrwx------. 1 user1 user1 64 Jul 7 22:00 1 -> /dev/tty1
lrwx------. 1 user1 user1 64 Jul 7 22:00 2 -> /dev/tty1
```

また、カレントディレクトリの状況は以下です。

```
$ pwd
/home/user1
$ ls
test.log
```

ここで、これから打つコマンドの出力を余すことなくすべてtest.logというファイルに出力したいものとします。
例として打つコマンドは、`ls -l test.log not_exist.txt`です。


##まず2>&1を使わない場合どうなるか
ファイルにコマンドの出力を流し込むには、リダイレクト（＞）を使うというのは大丈夫ですね？
test.logに書き込みたければ以下のように書くことができます。

```
$ ls -l test.log not_exist.txt > test.log
```

この出力結果は以下のようになります。

```
ls: cannot access not_exist.txt: No such file or directory
```

not_exist.txtというファイルはないというエラーだけが画面に出力されました。
ですが、`ls -l test.log`の結果部分が出力されていません。
お分かりかと思いますが、この結果部分はリダイレクトしたtest.logに出力されています。
確認してみましょう。

```
$ cat test.log
-rw-rw-r--. 1 user1 user1 0 Jul 7 22:10 test.log
```

これらの結果を整理すると次のようになります。

・エラーであれば画面に出力
・エラーでなければリダイレクト先に出力

これを図に直すと以下のようになります。
![02105435-B665-43A7-93E8-05402304811E_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1731510/ae859433-2139-789c-fcaf-332672cc2d14.jpeg)


今実行したいことは、コマンドの出力を余すことなくログファイルに出すことなので、これでは問題がありますね。
エラーメッセージも/home/user1/test.logに出るようにしたいです。

##次に2>&1を使った場合どうなるか
エラーメッセージの出力先を変えたいので、今度はアプローチを変えて以下のように記載してみます。

```
$ ls -l test.log not_exist.txt > test.log 2>&1
```

このコマンドを実行すると、今度は画面に何も出力されません。
では、test.logの中身はどうなっているでしょうか。

```
$ cat test.log
ls: cannot access not_exist.txt: No such file or directory
-rw-rw-r--. 1 user1 user1 0 Jul 7 22:15 test.log
```

ちゃんとエラーメッセージもログファイルに出力されていますね！
今回のデータの流れを図にすると以下のようになります。
![21BEB418-5771-48FB-A699-7D18190BAF90_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1731510/15fa5354-e94e-68e5-07cf-1e923914c4b7.jpeg)


標準エラー出力を標準出力と同じになるようにしたので、うまくログに出力されたわけですね。
2>&1をつけたときの挙動がご理解いただけましたでしょうか。


#まとめ
2>&1は、ファイルディスクリプタ２番（標準エラー出力）をファイルディスクリプタ１番（標準出力）と同じものにする際に使います。
すなわち、エラーメッセージと通常のコマンドの結果の出力先を同じにするために利用するのです。


#補足
本記事に対して２点の補足をいたします。

###2>&1の書く位置を変えるとどうなるか
まず、2>&1の書く位置についてです。
今回test.logに出力するという例をご紹介し、そこで以下のように記載しました。

```
$ ls -l test.log not_exist.txt > test.log 2>&1
```

これを、このように変えた時に、エラーとコマンドの結果の両方がファイルに書き込まれるでしょうか。

```
$ ls -l test.log not_exist.txt 2>&1 > test.log 
```

答えは「コマンドの出力結果しかファイルに書き込まれない」です。
なぜかは、ファイルディスクリプタ１番の複製を作った時に、そのファイルディスクリプタがどこを指していたかを考えればわかります。

コマンド実行前、ファイルディスクリプタ１番の参照先は以下のように/dev/tty1でした。

```
$ ls -l /proc/$$/fd　　　#現在のシェルのファイルディスクリプタを確認
total0
lrwx------. 1 user1 user1 64 Jul 7 22:00 0 -> /dev/tty1
lrwx------. 1 user1 user1 64 Jul 7 22:00 1 -> /dev/tty1
lrwx------. 1 user1 user1 64 Jul 7 22:00 2 -> /dev/tty1
```

ここから、ファイルディスクリプタ１番をtest.logへリダイレクトするよりも前に
ファイルディスクリプタ１番の複製→２番への付け替え、を行なっていることになるので、
結局複製時のファイルディスクリプタ１番の参照先は/dev/tty1のままであり、
複製結果できたファイルディスクリプタ２番もやはり/dev/tty1を指すことになります。

そしてその複製が終わった後で標準出力先をtest.logへリダイレクトしているので、
コマンド実行結果はファイルに書かれても、エラーメッセージは画面に出力されるという挙動になるのです。

###2>&1以外に標準エラー出力先を切り替える方法はあるか
実は例で示した方法以外にも、エラーとコマンド実行結果の両方を
test.logに出力する方法はいくつかあります。

まずは、&> または >& を使う方法です。このように書けます。

```
$ ls -l test.log not_exist.txt &> test.log
$ ls -l test.log not_exist.txt >& test.log
```

この場合の「＆」は、「標準出力と標準エラー出力の両方」という意味にとれるかと思います。

~~また、以下ように書いても同様の結果が得られます。~~
<font color="Red">この記載はやってはいけないようです。（以下の@angel_p_57さんのコメントに理由の記載があります）

```
$ ls -l test.log not_exist.txt > test.log 2> test.log
```