回答:
パイプは、カーネル内のファイルシステムで開かれたファイルであり、ディスク上の通常のファイルとしてはアクセスできません。特定のサイズにのみ自動的にバッファリングされ、いっぱいになると最終的にブロックされます。ブロックデバイスをソースとするファイルとは異なり、パイプはキャラクターデバイスと非常によく似ているため、通常はサポートlseek()
されず、通常のファイルのようにそれらから読み取られたデータを再度読み取ることはできません。
here-stringは、マウントされたファイルシステムで作成された通常のファイルです。シェルはファイルを作成し、その記述子を保持しますが、ファイルへのバイトの書き込み/読み取りが行われる前に、その唯一のファイルシステムリンクをすぐに削除(および削除)します。カーネルは、すべてのプロセスがすべての記述子を解放するまで、ファイルに必要なスペースを維持します。そのような記述子から読み取っている子がそうする能力を持っている場合、それは巻き戻されlseek()
、再び読むことができます。
どちらの場合も、トークン<<<
と|
はファイル記述子を表し、必ずしもファイル自体ではありません。次のようなことを行うことで、何が起こっているかをよりよく理解できます。
readlink /dev/fd/1 | cat
...または...
ls -l <<<'' /dev/fd/*
2つのファイルの最も大きな違いは、here-string / docがほぼすべて一度に行われることです。シェルは、読み取り記述子を子に提供する前にすべてのデータを書き込みます。一方、シェルは適切な記述子でパイプを開き、子を分岐してパイプの管理を行います。したがって、両端で同時に書き込み /読み取りが行われます。
ただし、これらの違いは一般的に正しいだけです。私が知っている限りでは(実際にはそれほどではありません)、これは、<<<
here <<
の単一の例外を除き、here-documentリダイレクトのhere-stringショートハンドを処理するほとんどすべてのシェルに当てはまりますyash
。yash
、busybox
、dash
、他とash
変異体は、ので、それらのシェルで本当にすべての後に両者の間にはほとんど違いがあるが、パイプでヒアドキュメントをバックアップする傾向があります。
わかりました-2つの例外。私はそれについて考えているので、ksh93
実際にはのためにパイプをまったく行わず|
、むしろビジネスw /ソケット全体を処理します- <<<*
他のほとんどのように削除されたtmpファイルを実行します。さらに、パイプラインの個別のセクションをサブシェル環境に配置するだけです。これは、少なくともサブシェルのように動作するため、POSIXのup曲表現の一種です。したがって、フォークすらしません。
実際、ここでの@PSkocikのベンチマーク(これは非常に便利です)の結果は、多くの理由で大きく異なる可能性があり、これらのほとんどは実装に依存しています。ヒアドキュメントのセットアップの最大の要因は、ターゲットの${TMPDIR}
ファイルシステムのタイプと現在のキャッシュ構成/可用性、さらに書き込まれるデータの量です。パイプの場合、必要なフォークのコピーが作成されるため、シェルプロセス自体のサイズになります。この方法でbash
は、パイプラインのセットアップでひどいです($(
コマンド)
置換を含めるため) -それは大きくて非常に遅いためですが、ksh93
それによってほとんど違いはありません。
シェルがパイプラインのサブシェルを分割する方法を示す別の小さなシェルスニペットを次に示します。
pipe_who(){ echo "$$"; sh -c 'echo "$PPID"'; }
pipe_who
pipe_who | { pipe_who | cat /dev/fd/3 -; } 3<&0
32059 #bash's pid
32059 #sh's ppid
32059 #1st subshell's $$
32111 #1st subshell sh's ppid
32059 #2cd subshell's $$
32114 #2cd subshell sh's ppid
パイプラインpipe_who()
コールが報告する内容と現在のシェルで実行されるレポートの違いは、(
サブシェルが展開さ)
れた$$
ときに親シェルのpidを要求するという指定された動作によるものです。けれどもbash
サブシェルは間違いなく別のプロセスがあり、$$
特別なシェルパラメータは、この情報の信頼できる情報源ではありません。それでも、サブシェルの子sh
シェルは、その正確なレポートを拒否しません$PPID
。
ベンチマークに代わるものはありません。
pskocik@ProBook:~
$ time (for((i=0;i<1000;i++)); do cat<<< foo >/dev/null; done )
real 0m2.080s
user 0m0.738s
sys 0m1.439s
pskocik@ProBook:~
$ time (for((i=0;i<1000;i++)); do echo foo |cat >/dev/null; done )
real 0m4.432s
user 0m2.095s
sys 0m3.927s
$ time (for((i=0;i<1000;i++)); do cat <(echo foo) >/dev/null; done )
real 0m3.380s
user 0m1.121s
sys 0m3.423s
そして、大量のデータの場合:
TENMEG=$(ruby -e 'puts "A"*(10*1024*1024)')
pskocik@ProBook:~
$ time (for((i=0;i<100;i++)); do echo "$TENMEG" |cat >/dev/null; done )
real 0m42.327s
user 0m38.591s
sys 0m4.226s
pskocik@ProBook:~
$ time (for((i=0;i<100;i++)); do cat<<< "$TENMEG" >/dev/null; done )
real 1m26.946s
user 1m23.116s
sys 0m3.681s
pskocik@ProBook:~
$ time (for((i=0;i<100;i++)); do cat <(echo "$TENMEG") >/dev/null; done )
real 0m43.910s
user 0m40.178s
sys 0m4.119s
パイプバージョンのセットアップコストは大きくなりますが、最終的にはより効率的になります。
echo foo >/dev/shm/1;cat /dev/shm/1 >/dev/null
両方のケースで高速に見えた
echo "$longstring"
または<<<"$longstring"
短い文字列では効率がそれほど重要ではないのかがわかりません。
cat <(echo foo) >/dev/null
は、よりも高速ですecho foo | cat >/dev/null
。