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


32

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

<command> > file.txt 2>&1

これは明らかstderrstdout最初にリダイレクトされ、次に結果stdoutはにリダイレクトされfile.txtます。

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

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


7
TLCLは、「最初に標準出力をファイルにリダイレクトし、次にファイル記述子2(標準エラー)をファイル記述子1(標準出力)にリダイレクトします」および「リダイレクトの順序が重要であることに注意してください。標準エラーのリダイレクト「常に標準出力をリダイレクトした後に発生しなければならないか、それは動作しません
Zanna

@Zanna:はい、私の質問は、TLCLでそれを読んだことからきました!:)私は、なぜそれが機能しないのか、つまりシェルが一般的なコマンドをどのように解釈するのかを知りたいと思っています。
トレインハートネット

、私はTLCLから理解してどのようなシェルは、stdoutをファイルに送信していることである-まあ、あなたの質問に、あなたは「これは明らかに...最初にstdoutに標準エラー出力をリダイレクトする」と反対を言った後(つまり、ファイルへの)標準出力に標準エラー出力。私の解釈では、stderrをstdoutに送信すると、端末に表示され、その後のstdoutのリダイレクトにはstderrが含まれません(つまり、stderrの画面へのリダイレクトはstdoutのリダイレクトが発生する前に完了しますか?)
Zanna

7
これは古い言い方ですが、シェルにはこれらのことを説明するマニュアルが付属してbashます。たとえばマニュアルのリダイレクトです。ところで、リダイレクトはコマンドではありません。
Reinierポスト

リダイレクトを設定する前にコマンド実行することはできませんexecv-family syscallを呼び出してサブプロセスを開始中のコマンドに実際に渡すと、シェルはループから抜け出します(そのプロセスでコードが実行されなくなります) )そして、その時点から何が起こるかを制御する方法はありません。リダイレクトは、このように、すべての必要があり、実行が開始される前に、シェルは、それがプロセスで実行されている自身のコピーがある一方で、実行することfork()で、あなたのコマンドを実行するために編を。
チャールズ・ダフィー

回答:


41

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

<command> > file.txt 2>&1標準出力が最初にすることで、ファイルにリダイレクトされ>、その後、2>&1ファイルである、stdoutが起こっている場所に標準エラー出力をリダイレクトします。

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


あなたは「DUP / fdreopen」のコールから順に実行されているファイル・ディスクリプタの観点から考えるとあれば、それは理にかなって左から右へ
マーク・K・コーワン

20

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

最初は、stderrとstdoutは同じものに行きます(通常、私がここで呼ぶ端末ですpts)。

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

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

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

そう:

  1. > file.txtfd/1今に行きfile.txtます。では>1何も指定されていない暗黙のファイルディスクリプタであるので、これはです1>file.txt

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1fd/2今、どこに行くfd/1 現在、行きます:

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

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

  1. 2>&1fd/2現在はどこに行っfd/1ても、何も変わらないことを意味します:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txtfd/1今に行きますfile.txt

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

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


ありがとう、これはもっと自然な説明のようです!:) 2.2番目のパートでは、少しタイプミスをしました。fd/1 -> file.txtとではありませんfd/2 -> file.txt
トレインハートネット

11

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

William Shottsによる Linux Command Lineによると

最初に標準出力をファイルにリダイレクトし、次にファイル記述子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ですのファイルへのリダイレクトが。もう一つの例

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

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

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


もっと雄弁!
16

ああ、今理解した!@Zannaと@Arronicalに感謝します!私のコマンドラインの旅に出発します。:)
トレインハートネット

@TrainHeartnetそれは喜びです!あなたは私限り、それを楽しんでいる希望:D
Zanna

@ザンナ:確かに、私は!:D
ハートネットを訓練する

2
@TrainHeartnet心配無用、欲求不満と喜びの世界があなたを待っています!
16

10

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

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

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

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

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

その他の利点もあります2>(xxx) (例:実行xxx中の新しいプロセスの作成、パイプの作成、新しいプロセスのファイル記述子0をパイプの読み取り側に接続し、元のプロセスのファイル記述子2をパイプの書き込み側に接続しますパイプ)。

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

回答

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

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

左から右へ。

<command> > file.txt 2>&1

  1. fork 新しいプロセスから。
  2. file.txtポインターを開いて、ファイル記述子1(STDOUT)に保存します。
  3. STDERR(ファイル記述子2)に、現在fd 1が指しているもの(これもfile.txtもちろん既に開かれている)をポイントします。
  4. exec その <command>

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

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


それが直接私が...今日何か新しいことを学んだ質問に答えていないが- 「ファイルハンドル魔法」ビットにUpvoting
フロリス

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

常に左から右に...

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

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

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

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


1
明確にするために、プロセスの置換とリダイレクトは2つの独立した操作です。片方をもう一方なしで行うことができます。プロセス置換を持つことは、bashが、それはリダイレクトを処理する順序を変更することを決定したことを意味するものではありません
muru
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.