Bashで2つのパイプラインを比較するにはどうすればよいですか?


143

Bashで一時ファイルを使用せずに2つのパイプラインを比較するにはどうすればよいですか?次の2つのコマンドパイプラインがあるとします。

foo | bar
baz | quux

そして、あなたdiffは彼らのアウトプットで見つけたいと思っています。1つの解決策は明らかに次のとおりです。

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

Bashで一時ファイルを使用せずにそれを行うことは可能ですか?パイプラインの1つをパイプしてdiffすることにより、1つの一時ファイルを削除できます。

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

ただし、両方のパイプラインを同時にdiffにパイプすることはできません(少なくとも明らかな方法ではありません)。/dev/fd一時ファイルを使用せずにこれを行うための巧妙なトリックはありますか?

回答:


146

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

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


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

「マジカル」/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

5
bashがなくても、一時的なfifoを使用できますmkfifo a; cmd >a& cmd2|diff a -; rm a
ハンマーを外す

引数の1つに通常のパイプを使用できます:pipeline1 | diff -u - <(pipeline2)。次に、出力は、2つの番号が付けられたfdsの代わりに-- /dev/stdinvs ++ /dev/fd/67か何かを表示することにより、どの入力であったかをより明確に思い出させます。
Peter Cordes

プロセス置換(foo <( pipe ))はファイルシステムを変更しません。 パイプは匿名です。ファイルシステムには名前がありません。シェルは、pipeではなく、システムコールを使用して作成しますmkfifostrace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'自分で確認したい場合は、ファイルおよびファイル記述子のシステムコールをトレースするために使用します。Linuxでは、仮想ファイルシステムの/dev/fd/63一部/procです。すべてのファイル記述子のエントリが自動的にあり、内容のコピーではありません。したがって、foo 3<bar.txt数えられない限り、これを「一時ファイル」と呼ぶことはできません
Peter Cordes

@PeterCordes良い点。見やすくするために、回答にコメントを含めました。
VonC 2018年

1
@PeterCordes編集はすべてあなたにお任せします。それがStack Overflowを面白くするものです。誰でも答えを「修正」できます。
VonC 2018年

127

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

例えば:

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

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


1
同じ解決策(匿名バッチ)に対する私の改訂版よりもはるかに詳細です。+1
VonC 2008

4
これは、Bashではプロセス置換と呼ばれます
フランクリンYu

5

このページにアクセスする一部の人々は、行ごとの差分を探しているかもしれません。そのため、commまたはgrep -f代わりに使用する必要があります。

指摘すべきことの1つは、回答のすべての例で、両方のストリームが完了するまで、diffは実際には開始されないということです。これを例えばでテストしてください:

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

これが問題である場合は、sd(ストリーム差分)を試すことができます。これはcomm、上記の例のように並べ替え(のように)やプロセス置換を必要とせず、grep -f 無限ストリームよりも数桁または大きさが速く、サポートされます。

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.