从文本文件中过滤掉比直接前一个路径更深的路径

从文本文件中过滤掉比直接前一个路径更深的路径

给定一个包含排序路径列表的文本文件,如何删除由于其父级(直接或不直接)也在列表中而导致冗余的所有路径?

例如:

/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 中使用子字符串,但不能保证父路径每次都处于同一级别,因此我无法使其工作。

答案1

我认为这应该可以做到。修改输入文件以添加更多案例

$ 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

答案2

以及强制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

sed如果您不介意顺序是否会恢复,则可以使用 GNU 扩展来更紧凑地完成此操作:

sed 'G;\_^\([^\n]*\)/.*\n\1\n_s/[^\n]*\n//;h;$! d;x;s/^\n//;s/\n$//'

解释如上,但使用换行符作为分隔符而不是添加#.

答案3

像这样的东西:

$ 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

perl -lne '$l=$_; grep $l =~ m|^\Q$_/|, @A or print, push @A, $_'
  • 我们累积了为给定行提供的所有不同路径,array @A它与已存储在其中的路径不匹配。
  • grepm|^\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

相关内容