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

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

ターミナルセッション(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が発生したという事実は、無関係な歴史的詳細です。

おすすめ記事