パスのソートされたリストを含むテキストファイルがある場合、親(即時かどうか)もリストに含まれているために冗長なすべてのパスを削除するにはどうすればよいですか?
例えば:
/aaa/bbb
/aaa/bbb/ccc
/ddd/eee
/fff/ggg
/fff/ggg/hhh/iii
/jjj/kkk/lll/mmm
/jjj/kkk/lll/mmm/nnn
次のように減らす必要があります。
/aaa/bbb
/ddd/eee
/fff/ggg
/jjj/kkk/lll/mmm
Awkで部分文字列を使用しようとしましたが、親パスが毎回同じレベルになるとは限らないため、機能させることができませんでした。
私はこれがそれをするべきだと思います。入力ファイルを変更して、さらにいくつかのケースを追加しました
_$ cat ip.txt
/aaa/bbb
/aaa/bbbd
/aaa/bbb/ccc
/ddd/eee
/fff/ggg
/fff/ggg/hhh/iii
/jjj/kkk/lll/mmm
/jjj/kkk/lll/mmm/nnn
/jjj/kkk/xyz
_
awk
の使用
_$ awk '{for (i in paths){if (index($0,i"/")==1) next} print; paths[$0]}' ip.txt
/aaa/bbb
/aaa/bbbd
/ddd/eee
/fff/ggg
/jjj/kkk/lll/mmm
/jjj/kkk/xyz
_
paths[$0]
_は、入力行をキーとする参照ですfor (i in paths)
すべての行が保存されているすべてのキーと比較されますif (index($0,i"/")==1) next
入力行が、行の先頭に_/
_が追加された保存済みキーと一致する場合は、その行をスキップします/
_は、_/aaa/bbbd
_が_/aaa/bbb
_と一致しないようにするために使用されますそして必須のsed
ソリューション:
sed '1s/^/#/;x;G;\_#\([^#]*\)#.*\n\1/_s/\n.*//;s/\n\(.*\)/\1#/;h;$! d;x;s/^#//;s/#$//;y/#/\n/'
スクリプトは、保留スペースにパスを収集します。新しい行ごとに、ホールドスペースがパターンスペースに追加され、すでに発生しているかどうかを確認します。
このソリューションは、文字#
がファイルで使用されていないことを前提としています。それ以外の場合は別の文字を使用するか、GNU sed
を使用する場合は、投稿の下部にある短いバージョンを使用してください。
詳細な説明:
1s/^/#/
移植性のために、#
文字を使用してホールドスペース内のパスを区切ります。最初の行では、最初の#
から始める必要があります
x;G
By exchanging the spaces and appending the hold space, we have the list of already occured buffers first, then the new path.
\_#\([^#]*\)#.*\n\1/_s/\n.*//
\_..._
アドレスが一致する場合、新しいパスは以前のパスのサブパスであるため、削除します。
s/\n\(.*\)/\1#/
スペースにはまだ改行があるので、パスは新しく、リストに追加します。
h;$! d
これが最後の行でない場合は、新しいリストを保留スペースに保存して、最初からやり直します。
x;s/^#//;s/#$//;y/#/\n/
最後の行では、最初と最後の#
を削除し、他の#
を改行に置き換えます。
GNU sed
の代替
これは、順序が元に戻されてもかまわない場合は、GNU sed
の拡張機能を使用してよりコンパクトに行うことができます。
sed 'G;\_^\([^\n]*\)/.*\n\1\n_s/[^\n]*\n//;h;$! d;x;s/^\n//;s/\n$//'
上記の説明ですが、#
を追加する代わりに改行を区切り文字として使用しています。
このようなもの:
_$ awk '{sub(/\/$/, "")}
NR != 1 && substr($0, 0, length(prev)) == prev {next};
{print; prev = $0"/" } ' paths
_
最初の行(_NR != 1
_)を除くすべてで、この行のプレフィックスをprev
に格納されている行(prev
の長さと同じ数の文字)と比較します。それらが一致する場合は、next
行にスキップします。それ以外の場合はprint
を出力し、この行をprev
に保存します。
ファイルがCロケールでソートされている、つまり_/
_が文字の前にあると仮定すると、またはディレクトリツリーのウォークによって生成された場合は、前に保存された行に対してテストするだけで十分です。ファイルが他のロケールで並べ替えられている場合、_/
_は並べ替えに影響を与えない可能性があり、_/aaa/bbb
_、_/aaaccc
_、_/aaa/ddd
_のような順序になります。ファイルがまったくソートされていない場合、サブディレクトリが親の前に来る可能性があり、問題は解決されません。
最初のsub(...)
は、末尾のスラッシュがある場合は行から削除します。行を格納するときに、ファイル名の一部が一致しないように、末尾にスラッシュを追加します。
@Sundeepによって投稿されたものに触発されたソリューション:
_awk -F / -v OFS=/ '
{
p = $0
while(--NF > 1) {
if ($0 in paths) next
}
print p
paths[p]
}' file
_
@Sundeepによって投稿された解決策は、入力パスの数N
のO(N^2)
です。上記のアプローチは、入力パスの最大深度D
でO(M)
です。これは、多数の入力パスに対して大幅に高速になるはずです。
すべてのパスが少なくとも9レベルの深さであることがわかっている場合は、もちろん、_--N > 1
_を_--N > 9
_に変更することで、上記を改善できます。
ちなみに、私のソリューションと@Sundeepによって投稿されたソリューションはどちらも、すべてのパスが正規化されていることを前提としています(つまり、_/foo/../../bar
_や_/foo//bar/baz
_のようなものはありません)。
Perl -lne '$l=$_; grep $l =~ m|^\Q$_/|, @A or print, Push @A, $_'
array @A
にすべての個別のパスを蓄積しますが、これはすでに格納されているものと一致しません。m|^\Q$_/|
は配列要素を引用し、一致するものを見つけます。sed -ne '
H # append current line into hold space
g # pattern space = hold space \n current line
y/\n_/_\n/ # change coordinate system
\|_\([^_]*\)_\(.*_\)\{0,1\}\1/|s/\(.*\)_.*/\1/ # match yes, strip current line
y/\n_/_\n/ # revert coordinate system
h # update hold space
$s/.//p # answer
'
/aaa/bbb
/ddd/eee
/fff/ggg
/jjj/kkk/lll/mmm