给定一个包含排序路径列表的文本文件,如何删除由于其父级(直接或不直接)也在列表中而导致冗余的所有路径?
例如:
/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
它与已存储在其中的路径不匹配。 - 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