web-dev-qa-db-ja.com

直前のパスよりも深いテキストファイルからパスを除外します

パスのソートされたリストを含むテキストファイルがある場合、親(即時かどうか)もリストに含まれているために冗長なすべてのパスを削除するにはどうすればよいですか?

例えば:

/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で部分文字列を使用しようとしましたが、親パスが毎回同じレベルになるとは限らないため、機能させることができませんでした。

5
Esker

私はこれがそれをするべきだと思います。入力ファイルを変更して、さらにいくつかのケースを追加しました

_$ 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_と一致しないようにするために使用されます
8
Sundeep

そして必須の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$//'

上記の説明ですが、#を追加する代わりに改行を区切り文字として使用しています。

5
Philippos

このようなもの:

_$ 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(...)は、末尾のスラッシュがある場合は行から削除します。行を格納するときに、ファイル名の一部が一致しないように、末尾にスラッシュを追加します。

4
ilkkachu

@Sundeepによって投稿されたものに触発されたソリューション:

_awk -F / -v OFS=/ '
{                  
    p = $0         
    while(--NF > 1) {
        if ($0 in paths) next
    }              
    print p        
    paths[p]       
}' file
_

@Sundeepによって投稿された解決策は、入力パスの数NO(N^2)です。上記のアプローチは、入力パスの最大深度DO(M)です。これは、多数の入力パスに対して大幅に高速になるはずです。

すべてのパスが少なくとも9レベルの深さであることがわかっている場合は、もちろん、_--N > 1_を_--N > 9_に変更することで、上記を改善できます。

ちなみに、私のソリューションと@Sundeepによって投稿されたソリューションはどちらも、すべてのパスが正規化されていることを前提としています(つまり、_/foo/../../bar_や_/foo//bar/baz_のようなものはありません)。

4
Satō Katsura
Perl -lne '$l=$_; grep $l =~ m|^\Q$_/|, @A or print, Push @A, $_'
  • 特定の行に提供されたarray @Aにすべての個別のパスを蓄積しますが、これはすでに格納されているものと一致しません。
  • grep 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
3
user218374