web-dev-qa-db-ja.com

NULLで終了するレコードでの通信の使用

別の質問に対する 回答 で、list2に表示され、list1に表示されないファイルを見つけるためにこのような構造を使用したいと思いました。

( cd dir1 && find . -type f -print0 ) | sort -z > list1
( cd dir2 && find . -type f -print0 ) | sort -z > list2
comm -13 list1 list2

しかし、私のバージョンのcommはNULLで終了するレコードを処理できないため、レンガの壁にぶつかりました。 (いくつかの背景:計算されたリストをrmに渡しているので、埋め込み改行を含む可能性のあるファイル名を処理できるようにする必要があります。)

簡単に作業できる例が必要な場合は、これを試してください

mkdir dir1 dir2
touch dir1/{a,b,c} dir2/{a,c,d}
( cd dir1 && find . -type f ) | sort > list1
( cd dir2 && find . -type f ) | sort > list2
comm -13 list1 list2

NULLで終了する行がない場合、ここでの出力は、./dにのみ表示される単一の要素list2です。

find ... -print0 | sort -zを使用してリストを生成できるようにしたいと思います。

list2に表示されるが、list1には表示されないNULLで終了するレコードを出力する、commに相当するものを再実装するにはどうすればよいですか?

1
roaima

GNU comm(GNU coreutils 8.25以降)には、そのための_-z_/_--zero-terminated_オプションがあります。

GNU commの古いバージョンの場合、NULとNLを交換できるはずです。

_comm -13 <(cd dir1 && find . -type f -print0 | tr '\n\0' '\0\n' | sort) \
         <(cd dir2 && find . -type f -print0 | tr '\n\0' '\0\n' | sort) |
  tr '\n\0' '\0\n'
_

そうすれば、commは改行で区切られたレコードでも機能しますが、入力の実際の改行はNULとしてエンコードされているため、改行を含むファイル名でも安全です。

GNUシステムと少なくともほとんどのUTF-8ロケールでは、同じように並べ替えるさまざまな文字列があり、ここで問題が発生するため、ロケールをCに設定することもできます¹。

これは非常に一般的なトリックです(commを使用した別の例については、 一致する行を反転、NULで区切る を参照)が、GNUシステムは比較的まれです。


¹例:

_$ touch dir1/{①,②} dir2/{②,③}
$ comm -12 <(cd dir1 && find . -type f -print0 | tr '\n\0' '\0\n' | sort) \
           <(cd dir2 && find . -type f -print0 | tr '\n\0' '\0\n' | sort)  
./③
./②
$ (export LC_ALL=C
    comm -12 <(cd dir1 && find . -type f -print0 | tr '\n\0' '\0\n' | sort) \
             <(cd dir2 && find . -type f -print0 | tr '\n\0' '\0\n' | sort))
./②
_

2019 edit:①②③の相対的な順序は、GNU libcの新しいバージョンで修正されましたが、????????を使用できます。 ????代わりに、たとえば、Unicodeコードポイントの95%などの問題がまだ残っている新しいバージョン(少なくとも2.30)では)

1