注:回答は、このサイトおよびunix.stackexchange.comのピアによる調査と回答の読み取りを通じて蓄積された、これらのメカニズムの最新の私自身の理解を反映しており、今後更新されます。質問をするか、コメントの改善を提案することをheしないでください。また、strace
コマンドを使用してシェルでsyscallがどのように機能するかを確認することもお勧めします。また、内部やsyscallの概念に怖がらないでください。シェルがどのように物事を行うのかを理解するためにそれらを知ったり使用したりする必要はありませんが、それらは間違いなく理解に役立ちます。
TL; DR
|
パイプはディスク上のエントリに関連付けられていないため、ディスクファイルシステムのinode番号はありません(ただし、カーネル空間のpipefs仮想ファイルシステムにinode があります) iノード。
- パイプは
lseek()
「使用できないため、コマンドは一部のデータを読み取って巻き戻すことはできませんが、リダイレクトする>
場合<
、または通常はファイルがlseek()
有効なオブジェクトであるため、コマンドは好きなようにナビゲートできます。
- リダイレクションは、ファイル記述子の操作であり、多くの場合があります。パイプには2つのファイル記述子しかありません-1つは左コマンド用、もう1つは右コマンド用です
- 標準ストリームとパイプのリダイレクトは両方ともバッファリングされます。
- パイプにはほとんどの場合フォークが関係するため、プロセスのペアが関係します。リダイレクト-常にではありませんが、どちらの場合も、結果のファイル記述子はサブプロセスに継承されます。
- パイプは常にファイル記述子(ペア)、リダイレクトを接続します-パス名またはファイル記述子を使用します。
- パイプはプロセス間通信の方法ですが、リダイレクトは開いているファイルまたはファイルのようなオブジェクトに対する単なる操作です
- どちらも
dup2()
、実際のデータフローが発生するファイル記述子のコピーを提供するために、フードの下にsyscallを採用しています。
- リダイレクトは
exec
組み込みコマンドで「グローバルに」適用できます(thisおよびthisを参照)。したがって、exec > output.txt
すべてのコマンドを実行するoutput.txt
とそれ以降に書き込みが行われます。|
パイプは、現在のコマンドにのみ適用されます(これは、単純なコマンド、サブシェルのような、seq 5 | (head -n1; head -n2)
または複合コマンドを意味します。
ファイルでリダイレクションが行われるecho "TEST" > file
と、などのecho "TEST" >> file
両方open()
がそのファイルでsyscallを使用し(を参照)、ファイル記述子を取得してに渡しdup2()
ます。パイプ|
はpipe()
、dup2()
syscall のみを使用します。
実行されるコマンドに関しては、パイプとリダイレクトはファイル記述子にすぎません-盲目的に書き込むか、内部で操作するファイルのようなオブジェクト(予期しない動作を引き起こす可能性があります; apt
たとえば、stdoutに書き込みさえしない傾向があります)リダイレクトがあることがわかっている場合)。
前書き
これら2つのメカニズムがどのように異なるかを理解するには、それらの本質的な特性、2つの背後にある歴史、およびCプログラミング言語のルーツを理解する必要があります。実際、ファイル記述子が何であるかdup2()
、およびpipe()
システムコールがどのように機能するかを知ることは不可欠lseek()
です。シェルは、これらのメカニズムをユーザーに抽象化する方法として意図されていますが、抽象化よりも深く掘り下げると、シェルの動作の本質を理解するのに役立ちます。
リダイレクションとパイプの起源
Dennis Ritcheの記事「Prophetic Petroglyphs」によると、パイプはMulticsオペレーティングシステムで作業していたときのMalcolm Douglas McIlroyによる1964年の内部メモに由来しています。見積もり:
私の最も強い懸念を簡単に言えば:
- ガーデンホースのようなプログラムを接続するいくつかの方法が必要です。別の方法でデータをマッサージする必要が生じたときに、別のセグメントにねじを締めてください。これもIOの方法です。
明らかなことは、その時点ではプログラムはディスクに書き込むことができましたが、出力が大きい場合は非効率的でした。Unix PipelineビデオでのBrian Kernighanの説明を引用するには:
まず、1つの大きな大規模なプログラムを作成する必要はありません-すでに仕事の一部を実行している既存の小さなプログラムを持っています...もう1つは、処理しているデータの量が次の場合に収まらない可能性があることですファイルに保存しました...幸運だったら、これらのディスクがあった時代に戻ったのを思い出してください、もし運がよければ、メガバイトまたは2つのデータを...パイプラインは出力全体をインスタンス化する必要がありませんでした。
したがって、概念的な違いは明らかです。パイプは、プログラムが互いに対話するメカニズムです。リダイレクト-基本レベルでファイルに書き込む方法です。どちらの場合も、シェルはこれらの2つのことを簡単にしますが、ボンネットの下には多くのことが行われています。
さらに深く:システムコールとシェルの内部動作
ファイル記述子の概念から始めます。ファイル記述子は、基本的に開いているファイル(ディスク上のファイル、メモリ内のファイル、または匿名ファイル)を表し、整数で表されます。2つの標準データストリーム (stdin、stdout、stderr)は、それぞれファイル記述子0、1、および2です。彼らはどこから来たのか ?さて、シェルコマンドでは、ファイル記述子は親-シェルから継承されます。そして、それは一般的にすべてのプロセスに当てはまります-子プロセスは親のファイル記述子を継承します。以下のためのデーモンは、すべての継承されたファイル記述子を閉じ、および/または他の場所にリダイレクトするのが一般的です。
リダイレクトに戻ります。それは本当に何ですか?これは、コマンドのファイル記述子を準備するようシェルに指示し(コマンドの実行前にシェルによってリダイレクトが行われるため)、ユーザーが提案した場所をポイントするメカニズムです。出力リダイレクトの標準定義は
[n]>word
その[n]
ファイルディスクリプタの数があります。するとecho "Something" > /dev/null
、1が暗示され、そしてecho 2> /dev/null
。
内部では、これはdup2()
システムコールを介してファイル記述子を複製することによって行われます。取りましょうdf > /dev/null
。シェルは、どこの子プロセスを作成しますdf
実行されますが、それが開きますその前に、/dev/null
ファイルディスクリプタ#3として、およびdup2(3,1)
ファイルディスクリプタ3のコピーを作成し、コピーが1になります。これは、発行されますあなたは2つのファイルを持っているか知っているfile1.txt
とfile2.txt
、cp file1.txt file2.txt
同じファイルを2つ作成しますが、それらを個別に操作できますか?ここでも同じことが起こっています。多くの場合、あなたは実行する前に、ことがわかりますbash
でしょうdup(1,10)
でコピーファイルディスクリプタ#1を作るためにstdout
(とそのコピーは#10のfdになります)、後でそれを復元するために。重要なのは、組み込みコマンドを検討する際に注意することです(シェル自体の一部であり、ファイルは/bin
どこにもありません)または非対話型シェルの単純なコマンド、シェルは子プロセスを作成しません。
そして、のようなものが[n]>&[m]
あり[n]&<[m]
ます。これはファイル記述子の複製です。これはdup2()
、シェル構文の場合と同じメカニズムであり、ユーザーが便利に利用できます。
リダイレクトに関して注意すべき重要なことの1つは、その順序が固定されていないことですが、シェルがユーザーの希望を解釈する方法にとって重要です。以下を比較してください。
# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/ 3>&2 2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd
# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/ 2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
シェルスクリプトでのこれらの実用的な用途は多岐にわたります。
その他多数。
配管pipe()
とdup2()
それでは、パイプはどのように作成されますか?pipe()
syscallを介してpipefd
、タイプint
(整数)の2つの項目で呼び出される配列(別名リスト)を入力として受け取ります。これらの2つの整数はファイル記述子です。pipefd[0]
パイプの読み出し側となり、pipefd[1]
書き込み終了となります。そうでdf | grep 'foo'
、grep
のコピーを取得するpipefd[0]
と df
のコピーを取得しますpipefd[1]
。しかし、どのように?もちろん、dup2()
syscall の魔法で。このdf
例でpipefd[1]
は、#4があるとしましょう。したがって、シェルは子を作成し、do dup2(4,1)
(私のcp
例を覚えてい ますか?)、do execve()
を実際に実行しdf
ます。当然、df
はファイル記述子#1を継承しますが、もはや端末を指していないことを認識しませんが、実際にはfd#4(実際にはパイプの書き込み側)です。当然、grep 'foo'
ファイル記述子の数が異なる場合を除き、同じことが起こります。
さて、興味深い質問:fd#1だけでなくfd#2もリダイレクトするパイプを作成できますか?はい、実際、それ|&
はbashで行われます。POSIX標準ではdf 2>&1 | grep 'foo'
、そのための構文をサポートするためにシェルコマンド言語が必要ですbash
が|&
、同様にサポートされています。
重要なのは、パイプが常にファイル記述子を処理することです。が存在するFIFO
か、名前付きパイプ、ディスク上のファイル名を持っており、あなたはファイルとしてそれを使用してみましょう、しかし、パイプのように振る舞います。しかし、|
パイプのタイプは匿名パイプと呼ばれるものです-実際には2つのオブジェクトが接続されているだけなので、ファイル名はありません。ファイルを扱っていないという事実も重要な意味を持っています。パイプは使用できませんlseek()
。メモリまたはディスク上のファイルは静的です-プログラムはlseek()
syscallを使用してバイト120にジャンプし、次にバイト10に戻り、最後まで転送できます。パイプは静的ではありません-連続しているため、パイプから取得したデータを巻き戻すことはできませんlseek()
。これにより、一部のプログラムがファイルまたはパイプから読み取りを行っているかどうかを認識できるため、効率的なパフォーマンスのために必要な調整を行うことができます。言い換えれば、prog
私がしなければ検出することができますcat file.txt | prog
かprog < input.txt
。その実際の作業例はtailです。
パイプのその他の2つの非常に興味深い特性は、Linuxで4096バイトのバッファーがあり、実際にLinuxソースコードで定義されているファイルシステムがあることです!それらはデータを渡すための単なるオブジェクトではなく、データ構造そのものです!実際、パイプとFIFOの両方を管理するpipefsファイルシステムが存在するため、 パイプにはそれぞれのファイルシステムにiノード番号があります。
# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/ | cat
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
Linuxでは、パイプはリダイレクトと同じように単方向です。Unixライクな実装では、双方向パイプがあります。シェルスクリプトの魔法がありますが、Linuxでも双方向パイプを作成できます。
参照:
thing1 > temp_file && thing2 < temp_file
はパイプでもっと簡単にやれると言った。しかし、コマンドやなどに>
これを行うために演算子を再利用しないのはなぜですか?なぜ余分な演算子ですか?thing1 > thing2
thing1
thing2
|