名前付きパイプ、ファイル記述子、およびEOF


10

同じユーザーの2つのウィンドウで、bashプロンプトが表示されます。ウィンドウ1で次のように入力します。

$ mkfifo f; exec <f

そのため、bashは名前付きパイプにマップされているファイル記述子0から読み取ろうとしていますf。ウィンドウ2で次のように入力します。

$ echo ls > f

ここでwindow-1はlsを出力してから、シェルが終了します。どうして?

次の実験:でもう一度window-1を開きますexec <f。ウィンドウ2で次のように入力します。

$ exec 3>f
$ echo ls >&3

上記の最初の行の後で、window-1が起動し、プロンプトを出力します。どうして?上述の第2行の後に、ウィンドウ1のプリントls出力とシェル滞在アライブ。どうして?実際、現在window-2でecho ls > f、window-1シェルを閉じません。

答えは、名前付きパイプを参照するwindow-2からのファイル記述子3の存在と関係がある必要がありますか?


1
exec <fbashからの読み取りを試みていない後、f最初に開こうとしいます。open()(パイプがインスタンス化され、シェルは、それからの入力を読み取り、その時点で)パイプに書き込みモードで別のオープンを行って、いくつかのプロセスがあるまで戻りません。
ステファンChazelas

素晴らしい点、@StéphaneChazelas。これが、一度exec 3>f実行すると、最初のシェルがプロンプトを表示する理由です。(マイナーポイント、コメントで「書き込みモード」という意味でしたか?)
Fixee

1
うん、ごめん。今わずか5分の締め切り前に編集
ステファンChazelas

回答:


12

これは、ファイル記述子のクローズに関係しています。

最初の例でechoは、シェルがと接続するために開く標準出力ストリームに書き込みf、終了すると、記述子が(シェルによって)閉じられます。受信側では、標準入力ストリーム(に接続されているf)から入力を読み取るシェルがを読み取りls、実行lsしてから、標準入力のファイルの終わり条件のために終了します。

名前付きパイプへのすべてのライター(この例では1つだけ)がパイプの終わりを閉じたため、ファイルの終わり条件が発生します。

2番目の例でexec 3>ff、にecho書き込むためにファイル記述子3を開き、次に書き込みますlsechoコマンドではなく、ファイル記述子が開かれているのはシェルです。記述子は、開くまで開いたままですexec 3>&-。受信側では、標準入力ストリーム(に接続されているf)から入力を読み取るシェルがを読み取りls、実行lsしてから、さらに入力を待ちます(ストリームがまだ開いているため)。

ストリームへのすべての書き込み(シェル、経由exec 3>f、およびecho)がパイプの終わりを閉じていないためexec 3>fまだ有効である)、ストリームは開いたままです。


echo上記のように、外部コマンドであるかのように書いています。ほとんどの場合、シェルに組み込まれています。それでも効果は同じです。


6

それほど多くはありません:パイプへの書き込みがない場合、読み取りに対して閉じているように見えます。つまり、読み取り時にEOFを返し、開いたときにブロックします。

Linuxのマニュアルページから(pipe(7)、ただし、も参照fifo(7)):

パイプの書き込み終了を参照するすべてのファイル記述子が閉じられている場合read(2)、パイプからの試行はファイルの終わりを参照します(read(2)0を返します)。

書き込みの終わりを閉じることは、の終わりで暗黙的に行わecho ls >fれることであり、もう1つのケースでは、ファイル記述子は開いたままになります。


Java(およびその他のオブジェクト指向言語)の参照カウントに似ているようです!しかし理にかなっています。
フィクシー:

2

@Kusalanandaと@ikkachuの2つの回答を読んだら、理解できたと思います。window-1では、シェルは何かがパイプの書き込み側を開いてから閉じるのを待っています。書き込み終了が開かれると、window-1のシェルはプロンプトを出力します。書き込み終了がクローズされると、シェルはEOFを取得して終了します。

window-2側では、私の質問で説明した2つの状況echo ls > fがあります。の最初の状況では、ファイル記述子3がないためecho、スポーンstdinstdoutます。

0 --> tty
1 --> f

次にecho終了し、シェルは両方の記述子を閉じます。ファイル記述子1が閉じて参照fしているため、の書き込み側fが閉じており、ウィンドウ1にEOFが発生します。

2番目の状況ではexec 3>f、シェルで実行し、シェルに次の環境を適用します。

bash:
0 --> tty
1 --> tty
2 --> tty
3 --> f

ここで実行するecho ls >& 3と、シェルechoは次のようにファイル記述子を割り当てます。

echo:
0 --> tty
1 --> f     # because 3 points to f
2 --> tty

次いで、シェルには、上記の3つの記述子を閉じてf、しかしf依然としてシェル自体からそれへの参照を有します。これが重要な違いです。exec 3>&-@Kusalanandaが指摘したように、記述子3をで閉じると、最後に開いた参照が閉じ、ウィンドウ1がEOFになります。


これは、変更する適切な設計上の理由がない限り、最初の3つのファイル記述子をそのままにしておくべき良い例です。他のシェルへの入力記述子(0)となった記述子(1)を使用すると、パイプ(およびその特定のデータストリームで実行していたこと)を閉じるだけでなく、2番目のシェルへの入力も閉じました終了させたシェル。これは問題ありませんが、意図的に行っている場合に限られます。番号の大きいファイル記述子を使用すると、特定の状態または定義されているとは想定されないため、このような副作用を回避できます。
Joe

正直なところ、そのコメントで何を言おうとしていたのかわかりません。削除します。
ステファンChazelas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.