web-dev-qa-db-ja.com

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

Bashで一時ファイルを使用せずに diff 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を含む巧妙なトリックはありますか?

135
Adam Rosenfield

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番目のバージョンでは、どの入力がどれであったかをより明確に思い出させます。
_-- /dev/stdin_ vs. _++ /dev/fd/63_または何か、2つの番号付きfdsの代わりに。


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

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

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

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

bashなしで、名前付きパイプを作成できます。 _-_を使用してdiffにSTDINから一方の入力を読み取り、もう一方として名前付きパイプを使用するように指示します。

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

パイプで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
_
136
VonC

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

例えば:

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

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

119
BenM

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

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

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

これが問題になる場合は、 sd (stream diff)を試すことができます。これは、ソート(commのような)も上記の例のようなプロセス置換も必要ありません。 grep -fよりも高速で、無限ストリームをサポートします。

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

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

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

ここに blogpost があります。これはsdを導入する端末でのストリームの差分について書いています。

5
mlg