148

How can you diff two pipelines without using temporary files in Bash? Say you have two command pipelines:

foo | bar
baz | quux

And you want to find the diff in their outputs. One solution would obviously be to:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

Is it possible to do so without the use of temporary files in Bash? You can get rid of one temporary file by piping in one of the pipelines to diff:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

But you can't pipe both pipelines into diff simultaneously (not in any obvious manner, at least). Is there some clever trick involving /dev/fd to do this without using temporary files?

4

3 に答える 3

156

2 つの tmp ファイル (必要なものではない) を含む 1 行は次のようになります。

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

ただし、 bashを使用すると、次のことを試すことができます。

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once


-- /dev/stdin2 番目のバージョンでは、2 つの番号付き fd の代わりにvs.++ /dev/fd/63または何かを表示することで、どの入力がどれであったかをより明確に思い出させます。


名前付きパイプでさえファイルシステムに表示されません.少なくともbashがファイル名を使用してプロセス置換を実装できるOSでは/dev/fd/63、コマンドが開いて読み取ることができるファイル名を取得して、bashが設定したすでに開いているファイル記述子から実際に読み取ることができます.コマンドを実行する前にアップします。(つまり、bash はpipe(2)fork の前に使用し、fd 63 での出力から の入力ファイル記述子にdup2リダイレクトします。)quuxdiff

「魔法の」/dev/fdまたはのないシステムでは/proc/self/fd、bashは名前付きパイプを使用してプロセス置換を実装する場合がありますが、一時ファイルとは異なり、少なくともそれら自体を管理し、データはファイルシステムに書き込まれません.

echo <(true)ファイル名から読み取る代わりにファイル名を出力するために、bashがプロセス置換を実装する方法を確認できます。/dev/fd/63典型的な Linux システムで印刷されます。または、bash が使用するシステム コールの正確な詳細については、Linux システムでこのコマンドを実行すると、ファイルおよびファイル記述子システム コールがトレースされます。

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

bash がなければ、名前付きパイプを作成できます。STDIN から 1 つの入力を読み取り、名前付きパイプをもう 1 つの入力として使用-するように指示するために使用します。diff

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

tee コマンドでは、1 つの出力のみを複数の入力にパイプできることに注意してください。

ls *.txt | tee /dev/tty txtlist.txt 

上記のコマンドは、ls *.txt の出力をターミナルに表示し、テキスト ファイル txtlist.txt に出力します。

ただし、プロセス置換を使用teeすると、同じデータを複数のパイプラインにフィードするために使用できます。

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar
于 2008-12-05T23:40:58.060 に答える
132

bash では、パイプラインを括弧で囲むことにより、サブシェルを使用してコマンド パイプラインを個別に実行できます。次に、これらの前に < を付けて、匿名の名前付きパイプを作成し、それを diff に渡すことができます。

例えば:

diff <(foo | bar) <(baz | quux)

匿名の名前付きパイプは bash によって管理されるため、(一時ファイルとは異なり) 自動的に作成および破棄されます。

于 2008-12-05T23:49:51.397 に答える
7

commこのページにたどり着いた人は、代わりにorを使用する必要がある行ごとの差分を探しているかもしれませんgrep -f

指摘すべきことの 1 つは、回答のすべての例で、両方のストリームが終了するまで差分が実際に開始されないことです。これを次のようにテストします。

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

これが問題である場合は、sd (ストリーム diff) を試すことができます。これは、並べ替えを必要とせず (必要な場合と同様comm)、上記の例のように置換を処理する必要もなく、よりもオーダーまたはマグニチュードが高速でgrep -f あり、無限ストリームをサポートします。

私が提案するテスト例は、次のように記述されsdます。

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

しかし、違いは、すぐseq 100に区別されることseq 10です。ストリームの 1 つが である場合tail -f、差分はプロセス置換では実行できないことに注意してください。

これは、ターミナルでのストリームの差分について書いたブログ投稿で、 sd.

于 2016-08-01T08:40:27.280 に答える