web-dev-qa-db-ja.com

シェルはコマンドとストリームリダイレクトをどの順序で実行しますか?

今日、stdoutstderrの両方をファイルにリダイレクトしようとしていましたが、これに遭遇しました。

<command> > file.txt 2>&1

これは明らかにstderrを最初にstdoutにリダイレクトし、次に結果のstdoutfile.txtにリダイレクトされます。

しかし、なぜ<command> 2>&1 > file.txtの順序ではないのですか?当然、これは最初に実行されるコマンド(左から右への実行を想定)、stderrstdoutにリダイレクトされ、次にstdoutfile.txtに書き込まれると読むでしょう。ただし、上記はstderrのみを画面にリダイレクトします。

シェルは両方のコマンドをどのように解釈しますか?

32
Train Heartnet

<command> 2>&1 > file.txtを実行すると、stderrは2>&1によってstdoutの現在の場所であるターミナルにリダイレクトされます。その後、stdoutは>によってファイルにリダイレクトされますが、stderrはリダイレクトされないため、端末出力のままになります。

<command> > file.txt 2>&1を使用すると、stdoutは最初に>によってファイルにリダイレクトされ、次に2>&1がstderrをstdoutが行く場所(ファイル)にリダイレクトします。

そもそも直観に反するように思えるかもしれませんが、この方法でリダイレクトを考え、左から右に処理されることを覚えておくと、はるかに意味があります。

41
Arronical

あなたがそれを追跡するなら、それは理にかなっているかもしれません。

最初は、stderrとstdoutは同じもの(通常、ここではptsと呼ぶ端末)に移動します。

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

ここでは、stdin、stdout、およびstderrをファイル記述子番号で参照しています。これらはそれぞれファイル記述子0、1、および2です。

さて、最初のリダイレクトのセットには、> file.txt2>&1があります。

そう:

  1. > file.txtfd/1file.txtに移動します。 >では、1は何も指定されていない場合の暗黙的なファイル記述子であるため、これは1>file.txtです。

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    
  2. 2>&1fd/2fd/1現在がどこにでも行くようになりました:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt
    

一方、2>&1 > file.txtを使用すると、順序が逆になります。

  1. 2>&1fd/2fd/1が現在行っている場所に移動するため、何も変更されません。

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
    
  2. > file.txtfd/1file.txtに移動しました:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    

重要な点は、リダイレクトは、リダイレクトされたファイル記述子がターゲットファイル記述子に対する将来のすべての変更に従うことを意味しないことです。 current状態のみを取ります。

20
muru

シェルが左側のリダイレクトを最初に設定し、次のリダイレクトを設定する前にcompleteを設定すると考えると役立つと思います。

The Linux Command Line by William Shotts

最初に標準出力をファイルにリダイレクトし、次にファイル記述子2(標準エラー)をファイル記述子1(標準出力)にリダイレクトします

これは理にかなっていますが、その後

リダイレクトの順序が重要であることに注意してください。標準エラーのリダイレクトは、標準出力のリダイレクト後に必ず発生する必要があります。そうしないと機能しません。

しかし、実際には、stderrを同じ効果でファイルにリダイレクトした後、stdoutをstderrにリダイレクトできます。

$ uname -r 2>/dev/null 1>&2
$ 

そのため、command > file 2>&1では、シェルはstdoutをファイルに送信し、stderrをstdout(ファイルに送信中)に送信します。一方、command 2>&1 > fileでは、シェルはまずstderrをstdoutにリダイレクトし(つまり、stdoutが通常行く端末に表示します)、その後、stdoutをファイルにリダイレクトします。 TLCLは、stdoutを最初にリダイレクトする必要があると言って誤解を招きます。stderrを最初にファイルにリダイレクトしてからstdoutを送信できるためです。できないことは、stdoutをstderrにリダイレクトすること、またはその逆beforeにリダイレクトすることです。もう一つの例

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

これはstdoutをstderrと同じ場所に廃棄すると思うかもしれませんが、そうではありません。stdoutを最初にstderr(画面)にリダイレクトし、次にstderrのみをリダイレクトします。

これが少し光をもたらすことを願っています...

11
Zanna

あなたはすでにいくつかの非常に良い答えを得ました。ただし、ここには2つの異なる概念が関係していることを強調しておきます。これらの概念の理解は非常に役立ちます。

背景:ファイル記述子とファイルテーブル

ファイル記述子は、0 ... nの数字で、プロセスのファイル記述子テーブルのインデックスです。慣例により、STDIN = 0、STDOUT = 1、STDERR = 2(ここでSTDINなどの用語は、一部のプログラミング言語およびマニュアルページで慣例により使用される単なるシンボル/マクロであり、STDINと呼ばれる実際の「オブジェクト」はありません。この議論のために、STDIN is 0など)。

そのファイル記述子テーブル自体には、実際のファイルが何であるかについての情報は一切含まれていません。代わりに、別のファイルテーブルへのポインタが含まれています。後者には、実際の物理ファイル(またはブロックデバイス、パイプ、またはLinuxがファイルメカニズムを介してアドレス指定できるもの)に関する情報と詳細情報(つまり、読み取り用か書き込み用か)が含まれます。

したがって、シェルで>または<を使用する場合は、それぞれのファイル記述子のポインターを単に置き換えて、他の何かを指すようにします。構文2>&1は、記述子2を1が指すところを指すだけです。 > file.txtは、単に書き込み用にfile.txtを開き、STDOUT(ファイルdecsriptor 1)がそれを指すようにします。

他にも便利なものがあります。 2>(xxx)(つまり、xxxを実行する新しいプロセスを作成し、パイプを作成し、新しいプロセスのファイル記述子0をパイプの読み取り側に接続し、元のプロセスのファイル記述子2をパイプ)。

これは、シェル以外のソフトウェアの「ファイルハンドルマジック」の基礎でもあります。たとえば、Perlスクリプトで、duplicatedを使用してSTDOUTファイル記述子を別の(一時的な)記述子に追加し、STDOUTを新しく作成された一時ファイルに対して再度開きます。この時点から、独自のPerlスクリプトおよびすべてのsystem()呼び出しからのすべてのSTDOUT出力は、その一時ファイルになります。完了したら、STDOUTを保存した一時記述子にre -dupできます。これで、すべては以前と同じです。その間に一時記述子に書き込むこともできるため、実際のSTDOUT出力は一時ファイルに出力されますが、実際にはreal STDOUT(通常はユーザー)に出力できます。

回答

上記の背景情報を質問に適用するには:

シェルはコマンドとストリームリダイレクトをどの順序で実行しますか?

左から右へ。

<command> > file.txt 2>&1

  1. forkは新しいプロセスをオフにします。
  2. file.txtを開き、そのポインターをファイル記述子1(STDOUT)に格納します。
  3. STDERR(ファイル記述子2)を、現在fd 1が指しているものに向けます(もちろん、既に開いているfile.txtです)。
  4. exec the <command>

これにより、明らかにstderrが最初にstdoutにリダイレクトされ、次に、結果のstdoutがfile.txtにリダイレクトされます。

oneテーブルしかない場合、これは理にかなっていますが、上で説明したように2つあります。ファイル記述子は相互に再帰的にポイントしていないため、「STDERRをSTDOUTにリダイレクトする」と考えるのは意味がありません。正しい考え方は、「STDERRがSTDOUTの指す場所を指す」ことです。後でSTDOUTを変更した場合、STDERRはそのままであり、STDOUTにさらに変更が加えられても魔法のようには進みません。

10
AnoE

常に左から右に...

Mathと同様に、乗算と除算が加算と減算の前に行われることを除いて左から右に行われます。ただし、括弧(+-)内の演算は乗算と除算の前に行われます。

ここのBash初心者ガイド( Bash初心者ガイド )にあるように、最初に来るもの(左から右の前)の階層には8つの順序があります。

  1. ブレース展開「{}」
  2. チルダ展開「〜」
  3. シェルパラメーターと変数式「$」
  4. コマンド置換 "$(command)"
  5. 算術式「$((EXPRESSION))」
  6. プロセス置換ここで話していること "<(LIST)"または ">(LIST)"
  7. 単語分割 "'<スペース> <タブ> <改行>'"
  8. ファイル名展開「*」、「?」など.

だから、それは常に左から右です...時を除いて...

3

順序は左から右です。 Bashのマニュアルは、あなたが尋ねる内容をすでにカバーしています。マニュアルのREDIRECTIONセクションから引用:

   Redirections  are  processed  in  the
   order they appear, from left to right.

そして数行後:

   Note that the order of redirections is signifi‐
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli‐
   cated from the standard output before the stan‐
   dard output was redirected to dirlist.

コマンドが実行される前にリダイレクトが最初に解決されることに注意することが重要です! https://askubuntu.com/a/728386/295286 を参照してください

3