如果父目录也在列表中,则从列表中删除路径

如果父目录也在列表中,则从列表中删除路径

我的标题措辞可能有点奇怪,所以这是我的情况:我有一堆目录路径,例如

/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/baror /foo/bar/,而不是例如 /foo/asdf/../baror /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
'

相关内容