#はじめに
本記事では、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というものを書いてきた方もおられるでしょう。
この記号ですが、どういう意味だかご存知でしょうか。
さまざま解釈はあるかと思いますが、おおよそ以下のような解釈になります。
ファイルディスクリプタ2番の出力先をファイルディスクリプタ1番を複製したものに付け替える
はい、意味不明ですよね...
でも、これからこれについて説明をしていくので安心してください。
##2>&1を分解して考えてみる
まずはこのへんてこりんな記述を分解して考えてみましょう。
それぞれが意味するものを分けて書くと、以下のようになります。
・2:ファイルディスクリプタ2番
・>:出力のリダイレクト
・&:ファイルディスクリプタを複製する
・1:ファイルディスクリプタ1番
##2>&1とした時の挙動を確認してみる
記号ばかり眺めていてもわかりにくいので、もう少し挙動を可視化してみたいと思います。
普段、プロセスのファイルディスクリプタ0、1、2番は、それぞれ、
標準入力、標準出力、標準エラー出力に繋がっています。
図にするとこんな感じ。
この図の状態から、2>&1を使用すると次のような挙動になります。
①ファイルディスクリプタ1番のコピーが作成される
②開かれているファイルディスクリプタ2番が閉じられる
③①でコピーされたものがファイルディスクリプタ2番となる
順番に図解していきます。
これで完成です!
では、2>&1とする前と後ではどこが変わったでしょうか。
正解は「標準エラーの出力先が変わっている」です。
2>&1の使用前は、ファイルディスクリプタ2番を指定すると、/dev/tty1にデータが流されるようになっていました。
ですが、2>&1使用後は、/home/user1/test.logに流されるようになっています。
つまり、ファイルディスクリプタ1番が複製され、ファイルディスクリプタ2番がそれに付け替わったわけです。
#2>&1はどんな時に使うのか
挙動は何となく分かっていただけたかと思いますが、では実際にどのような時に使うのでしょうか。
Linuxでは、どのプロセスもエラーが発生するとファイルディスクリプタ2番にエラーメッセージを流すという共通の挙動を取ります。
つまり、エラー文言はすべてファイルディスクリプタ2番で指定されたファイルに出力されるのです。
(端末画面の入出力もデバイスファイルというファイルを介して行われます)
よって、2>&1を使うシーンとしては、
エラーメッセージの出力先を変更したい時。
かつ、その出力したい先がファイルディスクリプタ1番の出力先と同じ時。
となります。
それがどういうことか、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
これらの結果を整理すると次のようになります。
・エラーであれば画面に出力
・エラーでなければリダイレクト先に出力
今実行したいことは、コマンドの出力を余すことなくログファイルに出すことなので、これでは問題がありますね。
エラーメッセージも/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
ちゃんとエラーメッセージもログファイルに出力されていますね!
今回のデータの流れを図にすると以下のようになります。
標準エラー出力を標準出力と同じになるようにしたので、うまくログに出力されたわけですね。
2>&1をつけたときの挙動がご理解いただけましたでしょうか。
#まとめ
2>&1は、ファイルディスクリプタ2番(標準エラー出力)をファイルディスクリプタ1番(標準出力)と同じものにする際に使います。
すなわち、エラーメッセージと通常のコマンドの結果の出力先を同じにするために利用するのです。
#補足
本記事に対して2点の補足をいたします。
###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
答えは「コマンドの出力結果しかファイルに書き込まれない」です。
なぜかは、ファイルディスクリプタ1番の複製を作った時に、そのファイルディスクリプタがどこを指していたかを考えればわかります。
コマンド実行前、ファイルディスクリプタ1番の参照先は以下のように/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
ここから、ファイルディスクリプタ1番をtest.logへリダイレクトするよりも前に
ファイルディスクリプタ1番の複製→2番への付け替え、を行なっていることになるので、
結局複製時のファイルディスクリプタ1番の参照先は/dev/tty1のままであり、
複製結果できたファイルディスクリプタ2番もやはり/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
この場合の「&」は、「標準出力と標準エラー出力の両方」という意味にとれるかと思います。
また、以下ように書いても同様の結果が得られます。
この記載はやってはいけないようです。(以下の@angel_p_57さんのコメントに理由の記載があります)
$ ls -l test.log not_exist.txt > test.log 2> test.log