シェルスクリプトの 2>&1 について理解する

シェルスクリプトの 2>&1 について理解する

スクリプトについて学んでいると、 2>&1 という表現に出会います。

ちょっと調べると「標準エラー出力を標準出力に吐き出す」などと書いてあります。

これについていろいろ勉強して、ようやく腑に落ちるところまで理解できたので記事にしてみます。

前提となる知識

  • > はリダイレクションと呼ばれ、コマンドの出力先を変更することができます。通常は、画面に出力しますが、 > を使用すると出力先をファイルなどに変更できるため、ファイルに内容を書き出すことができます。 > は上書き、 >> は追記です。
  • 2> の頭についている 2 は「ファイルディスクリプタ」と言われているものです。これは、プログラムがファイルを操作する際に、操作対象のファイルを識別するためにつけられる整数の番号です。1 と 2 は特別で、1は「標準出力」、2は「標準エラー出力」の番号と決められています。たとえば ls 2> err.txt とすると、 コマンド ls の標準エラー出力(ファイルディスクリプタ2)が err.txt にリダイレクトされます。
  • &1& はファイルディスクリプタの数字の前につけることで、「ファイルディスクリプタ1番」ということを意味します。変数が $var と書いてあると、「変数var」とわかるのと同じようなイメージで見ておくといいでしょう。
  • ファイルディスクリプタのうち、1 は省略可能です。

ls > stdout.txtls 1> stdout.txt と同義

ファイル・ディレクトリの一覧を表示する ls は、通常は結果を画面に出力しますが、 > を使うことで、ファイルに出力できることは上述しました。実は、シンプルに > と記載している時は、 1> を意味しています。なので、 ls > stdout.txtls 1> stdout.txt と同義です。これは、「ls の標準出力(ファイルディスクリプタ1)を stdout.txt にリダイレクトしなさい」となります。標準エラー出力を stderr.txt にリダイレクトしたい場合は、 ls 2> stderr.txt となります。

ls > stdout.txt 2>&1 の意味

しかし、ときにプログラムの結果を標準出力も標準エラー出力も同じファイルに出力したいときがあります。そういう時は、こう考えます。

  • 標準出力 (ファイルディスクリプタ1) を、ファイルにリダイレクトします。
  • 続いて、標準エラー出力 (ファイルディスクリプタ2) を、ファイルディスクリプタ1に向けます。

このデモのために以下のような環境を作ってみましょう。 tmp というディレクトリを作成し、その中に abc.txt というファイルを作成します。

mkdir tmp
cd tmp
touch abc.txt

ここで、コマンドが成功する場合と失敗する場合を示します。 ls の引数に存在するファイルを指定すればそのファイルが出力され、存在しないファイルを指定すればエラーが出力されます。まずは画面で確認します。

ls abc.txt
# abc.txt として表示される
abc.txt
ls def.txt
# def.txt は存在しないので以下のエラーメッセージが出る
ls: 'def.txt' にアクセスできません: そのようなファイルやディレクトリはありません

ここで、 “abc.txt” が標準出力、 “ls: ‘def.txt’ にアクセスできません: そのようなファイルやディレクトリはありません” が標準エラー出力となります。

それでは、標準出力の結果を stdout.txt として出力します。普通ならば、 ls abc.txt > stdout.txt とするところですが、ファイルディスクリプタを理解するために、以下のようにします。

ls abc.txt def.txt 1> stdout.txt
cat stdout.txt
# abc.txt と表示される
# エラーの表示は記録されていない

次に、標準エラー出力の結果を stderr.txt として出力します。ファイルディスクリプタ2 を指定することで出力できます。

ls abc.txt def.txt 2> stderr.txt
cat stderr.txt
# ls: 'def.txt' にアクセスできません: そのようなファイルやディレクトリはありません 
# と表示される
# 先ほどとは逆に abc.txt は表示されない

それでは、この結果をどちらも output.txt に出力したいとします。この際、以下のようにします。

  • まず、 ls abc.txt def.txt の標準出力 (ファイルディスクリプタ1) を、端末でなく output.txt にリダイレクトします。
  • 次に、標準エラー出力 (ファイルディスクリプタ2) を、ファイルディスクリプタ1 (&1 で表現される) に向けます。この時、ファイルディスクリプタ1 はすでに output.txt を指しているため、標準エラー出力も output.txt に出力されます。 この順番が大事です。 まず標準出力をファイルにリダイレクトし、その後、標準エラー出力を標準出力に向けるのです。
ls abc.txt def.txt 1> output.txt 2>&1

今、 1> の “1” は省略できるのでした。なので、これを省略すると以下になります。

ls abc.txt def.txt > output.txt 2>&1

もし、2>&1 を前に持ってきたらどうなるでしょうか。

ls abc.txt def.txt 2>&1 > output.txt 

この場合、まず標準エラー出力 (ファイルディスクリプタ2) が標準出力 (ファイルディスクリプタ1) に向けられます。 しかし、この時点では ファイルディスクリプタ1 はまだどこにもリダイレクトされていないため、デフォルトの端末を指しています。 結果として、標準エラー出力は端末に表示されます。その後、 > output.txt (つまり 1> output.txt )によって標準出力が output.txt にリダイレクトされますが、このリダイレクトは標準エラー出力には影響しません。したがって、output.txt には標準出力のみが記録され、標準エラー出力は端末に表示されたままになります。

「まず ファイルディスクリプタ1 をファイルにリダイレクト。その後、ファイルディスクリプタ2 を ファイルディスクリプタ1 (&1) に向ける」 と理解することで困ることが減るのではないでしょうか。

補足: Bash 4.0以降の省略記法

Bash 4.0以降では、標準出力と標準エラー出力の両方を同じファイルにリダイレクトする際に、より簡潔な記法が使用できます。

ls abc.txt def.txt &> output.txt
# これは > output.txt 2>&1 と同じ意味

この &> という記法は、一見すると意味が分かりにくいかもしれませんが、標準出力も標準エラー出力も両方リダイレクトするという便利な省略形です。ただし、この記法を理解するためにも、本記事で説明した 2>&1 の仕組みを理解しておくことが重要です。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください