我的标题措辞可能有点奇怪,所以这是我的情况:我有一堆目录路径,例如
/a/b
/a/b/c
/a/b/c/d
/a/e/f/g/h
/a/e/f/g/h/i/j/k/l
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
我想过滤掉列表中已存在的条目的子路径的所有行,例如
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
目录路径是从 获取的find
,因此它们应该可靠地按自上而下的顺序排列。解析为数组或多行字符串的解决方案都受到欢迎。
答案1
我假设路径名列表可能未排序,并且生成的路径名列表应与输入中的顺序相同。我还假设没有路径名包含嵌入的换行符。
使用/bin/sh
:
#!/bin/sh
set --
while IFS= read -r pathname; do
for p do
case $pathname in ("$p"/*) continue 2 ;; esac
done
set -- "$@" "$pathname"
done <list
printf '%s\n' "$@"
这将从文件中读取路径名list
,一次一行。接受的路径名(最初是一个空列表)针对每个读取的路径名进行测试,在内部循环中一次测试一个。如果接受的路径名是当前路径名的目录路径前缀,则当前路径名将被丢弃(内部循环使用 跳到外部循环的下一次迭代continue 2
)。如果没有发现接受的路径名是当前路径名的目录路径前缀,则接受当前路径名。
接受的路径名列表保存在位置参数中。
shellbash
显然能够运行上面的脚本,但是如果您想要专门为该 shell 编写的内容,您可以说
#!/bin/bash
accepted=()
while IFS= read -r pathname; do
for p in "${accepted[@]}"; do
[[ $pathname == "$p"/* ]] && continue 2
done
accepted+=("$pathname")
done <list
printf '%s\n' "${accepted[@]}"
使用awk
与上面相同的方法:
$ awk '{ for (i=1; i<=n; ++i) if (index($0, accepted[i] "/") == 1) next; accepted[++n]=$0 } END { for (i=1; i<=n; ++i) print accepted[i] }' list
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
代码awk
,nicified:
{
for (i = 1; i <= n; ++i)
if (index($0, accepted[i] "/") == 1)
next
accepted[++n] = $0
}
END {
for (i = 1; i <= n; ++i)
print accepted[i]
}
您应该能够awk
在一开始就看到该程序与 shell 代码变体之间明显的相似之处。
这用于index()
测试接受的路径名是否是当前路径名的前缀。您本来可以使用if ($0 ~ "^" acceped[i] "/")
它,但这样做的缺点是路径名本身被用作正则表达式的一部分。一旦您的路径名包含诸如等字符,这一点就变得很.
重要*
。
答案2
如果我没记错的话,规范化(*)的列表,或者至少一致呈现的路径,按通常的字典顺序排序,目录的子目录会立即出现在该目录之后(递归地)。因此,只查看前一行(未删除的)就足够了。
(* 通过标准化,我的意思是/foo/bar
or /foo/bar/
,而不是例如
/foo/asdf/../bar
or /foo///bar//
。 的输出find
不会成为问题,因为如果给定非标准化起始目录,它确实会给出非标准化输出,但输出至少是一致的。)
一个路径仍然可以是另一个路径的前缀,同时只是同级而不是父级,例如/foo
和/foobar
。为了处理这种情况,我们可以在每一行还没有尾部斜杠的情况下添加尾部斜杠。
因此(将/foo
和/foobar
添加到测试中,并且不尝试编写代码):
$ sort paths.txt | awk '! /\/$/ { $0 = $0 "/" }
last && last == substr($0, 1, length(last)) { next; }
{ last = $0; sub(/\/$/, "", $0); print }'
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
/foo
/foobar
$0
如果需要,第一行将斜杠添加到当前行;第二个将该行与最后存储的行(在 中last
)进行比较(如果有的话),并删除匹配的行;第三个存储并打印所有未删除的行,并删除尾部斜杠。 (删除sub(...)
以保留它们。)
答案3
一个短awk解决方案:
<infile sort -u |awk 'NR==1 || index($0, pre"/")!=1{print; pre=$0}'
答案4
GNU sed使用扩展正则表达式模式-E
。没有子集的前一行存储在保留空间中。
< file sort \
| sed -En '
G
/^([^\n]+)\/.*\n\1$/d
s/\n.*//p;h
'
< file sort \
| perl -lne '
$prev //= $_;
print($prev = $_)
if index($_, "$prev/");
'
POSIX sed 不允许[^\n]
,所以我们用 POSIX 兼容的结构重写
< file sort \
| sed -e '
H;x
\|^\(..*\)\n\1/|{
s/\n.*//;h;d
}
g
'