パイプとサブシェルのリダイレクトを理解するのに苦労しています。コードの説明を高く評価します。

パイプとサブシェルのリダイレクトを理解するのに苦労しています。コードの説明を高く評価します。

ターミナルセッション(Debian Buster、Bash 5.0)で、次のログを検討してください。

root@cerberus ~/scripts # rm -f result
root@cerberus ~/scripts # { { echo test; } | cat > result; }
root@cerberus ~/scripts # cat result
test
root@cerberus ~/scripts #

ここには特別なものはありません。これは予想される動作であり、理解しています。

ただし、次の場合の動作を理解していません。

root@cerberus ~/scripts # rm -f result
root@cerberus ~/scripts # { { echo test >&3; } | cat > result; } 3>&1
test
root@cerberus ~/scripts # cat result
root@cerberus ~/scripts #

正確に言うと、2行目を実行したときに「test」が出力される理由はわかりますが、結果ファイルに何もない理由はわかりません。何が起こっているのか私の理解は次のとおりです。

  1. まず、fd 3はのコピーに設定されますstdout。パイプラインが実行される前にこれが起こると確信しています。そうしないと、パイプライン内のどのコマンドもfd 3にアクセスできなくなり、「無効な記述子」エラーメッセージが表示されるためです。

  2. パイプは単純なコマンドではないため、これを実行するにはサブシェルを作成する必要があります。子シェルは、ファイル記述子やリダイレクトを含む親シェルの実行環境を継承します。[1]

  3. パイプラインの各コマンドは、独自のサブシェルでも実行されます。[2]、実行環境とファイル記述子を再継承します。の出力echoはfd 3にリダイレクトされ、fd 3は以前からコピーされますstdout。つまり、出力はfd 3に移動し、次にfd 1、つまりstdoutに移動します。echostdout

  4. echoただし、出力が結果ファイルに含まれない理由はわかりません。 ~からバッシュマニュアル(強調):

パイプラインの各コマンド出力は、次のコマンドの入力にパイプされます。つまり、各コマンドは前のコマンドの出力を読み込みます。この接続は、コマンドで指定されたリダイレクトの前に行われます。

私の理解は、echo出力がcat入力に接続される必要があるということです今後>&3リダイレクトを個別に設定または適用します。しかし、これが真であれば、コマンドを実行した後に結果ファイルが存在し、「テスト」が含まれます。だから私の理解は間違いなく間違っています。

誰かが私が逃したことを説明できますか?

以下のABとGillesの優れた回答に基づいて、追加の説明で更新

私の懸念は、上記の#3に書かれた内容から来ています。しかしそれは真実ではない。 Gilesの答えも参照してください。

ABは最初に答えを提供しました(下記参照)。しかし、それを理解するには少し時間がかかりました。それで、わかりやすくするためにいくつかの節を説明します。

  1. 行の最後の部分:3>&1最初に完了:ターミナル出力を指すfd 1がfd 3にコピーされます。これは fd 1 と fd 3 の両方が端末出力を指すことを意味します。それらは同じで、互いを変えて使うことができます。

  2. 分岐する前に、システムコールは通常、次の使用可能なfd(pipe(2)fd 4とfd 5)にパイプを作成するために使用されます。その後、準備プロセスは future echo と future cat に分岐し、次のステップを実行します。

    a) 準備プロセス echo は次のように動作します。

    fd 5がfd 1にコピーされます(fd 1:ターミナル出力が指す位置を上書きします)。これは、fd 1 が fd 5 と同じで、互いに置き換えて使用できることを意味します。具体的には、fd 1はもはや端子出力を指すのではなく、パイプの書き込み端を指します。このステップ(以下を参照)では、fd 1への書き込みはその書き込み端を指すため、出力はパイプ

    echo書き込み端に移動します。同じ操作で2つのファイル記述子を使用する必要はなく、とにかくfd 1が書き込まれるため、fd 5が閉じます。その後、後述の追加のリダイレクトを設定した後に実行されます(3.を参照)。 b) 同様に、fd 4 から fd 0 への準備プロセスをコピーすることは、fd 0 がもはや端末入力を指すことなくパイプの受信端を指すことを意味する。このステップでは、fd 0を読み取り、fd 0が対応する受信端に接続されているため、入力はパイプの受信端から出ます。同じことを示すために、2つのファイル記述子が必要なく、とにかくfd 0から読み取られるので、fd 4は閉じられます。それから実行されました。これが起こると、fd 3はどこからでも継承されます。echo

    echo

    echo

    catcatcatcatcat

  3. >&3箇条書き1の反対:fd 3をfd 1にコピーします。 fd 3 はターミナル出力を指すように生成され、パイプを実行するサブシェルと個々のパイプコマンドを実行する他のサブシェルから継承されます。

    2a)段階において、fd1はパイプの書込み側を指した。ただし、リダイレクトは>&3再びfd 1を上書きし、fd 3と同じになり、これは(まだ)端末出力を指します。これは、fd 1 がパイプの書き込み端を指すことなく端子出力を指すことを意味します。これがパイプを実行すると、端末に「test」が表示される理由です(echofd 1が指す位置に関係なく、常にfd 1が書き込まれることに注意してください)。

    また、fd 1がリダイレクトによって「上書き」されると、以前のバージョンは閉じられます(デフォルトのシステムコールがdup2(2)これを実行するため)。以前のバージョンはパイプの書き込みの終わりを指すので、その書き込みの終わりは閉じます。

    したがって、受信側ではcatいかなるデータも受信しません。すぐにEOF通知を受け取ります。これがまさにcat何も受け取らず、結果ファイルが空であるか切り捨てられたままである理由です。 [注:上記のようにfd 1が記録され、fd 3に関する情報がまったく知られていないため、

    リダイレクト後にfd 3を閉じる必要があります(つまり、作成する必要があります>&3 3>&-)。しかし、私の例ではその部分が欠けていて、実際の問題を妨げないようにそのままにしたかったのです。)]>&3echo

答え1

1.まず、fd 3はstdoutのコピーに設定されます。

上でおっしゃったとおりに合う言葉ですが、ちょっとおかしいですね。この言葉の意味を間違って理解したようです。これは、fd 3に書き込むことが、そのリダイレクトが適用されている間にstdoutに書き込むのと同じであるという意味ではありません。これは、fd 3がリダイレクトを設定すると接続されたstdoutに接続されることを意味します。このコードを端末で実行している場合は、3>&1ファイル記述子3を端末に接続します。だから…

3. echo(…) の出力は fd 3 にリダイレクトされ、fd 3 は以前 stdout からコピーされました。つまり、これによりecho出力がstdoutに表示されます(出力はfd 3に移動し、fd 3はfd 1に移動します)。 、すなわち標準出力)。

FD 3は端末です。ある時点で他のプロセスにもfd 1が発生したという事実は、無関係な歴史的詳細です。

答え2

OPの4番目のためにこのように動作します。FDプロセスに応じたさまざまな作成/実行継承。私はfork / execが起こるすべての場所を書いていません。私は間違いなくこれらのいくつかを単純化しています(組み込みコマンドを使用して...)。 Linux用のドキュメントリンクが提供されていますが、POSIXまたはPOSIXのようなシステムでも同じ動作が発生するはずです。

  1. 行の最後の部分:3>&1最初に完了:FD端末を指す 1 は、次のようにコピーされます。FD3(通常使用dup2(2)システムコール)。
  2. フォークする前に、通常、次のようにパイプラインが作成されます。pipe(2)システムコール、次回可能FDs:4と5を想定しましょう。その後、準備プロセスは futureechoと future に分けられますcat。 proto-echoは5を1にコピーし(それが指す場所である端末を「上書きします」)、5を閉じてexecsしecho、proto-cat dups2()は4を0に閉じ、4を閉じてexecsしますcatFD3 どこからでも継承されます。
  3. >&3ポイント1とは逆に:繰り返されます。FD3(ターミナルを指す)FD1. したがって、パイプの書き込み側が交換され、閉じています(dup2(2)言う:「ファイル記述子新しいFD以前に開いた場合は、再利用するまで自動的に閉じます。 ))パイプには何も記録されません。端末はそれを受信して​​表示しますtest
  4. catターゲットファイルを並列に開いて切り取り、resultパイプから読み取りを開始します。これでEOFが実行されます。pipe(7)書き込み側が閉じているためcatコマンドは終了しました。
  5. デフォルトのシェルプロセスに残りのサブプロセスはありません。実行が終了します。

結果:test端末にresultファイルが空です。

関連情報