rm -r:排除某些子目录

rm -r:排除某些子目录

问题

我有一个目录结构,例如像这样(这里尾随斜杠代表文件夹):

./A
./B/A/A
./B/A/B
./B/B/
./B/C
./C/

我需要递归删除除某些文件和目录之外的所有内容:

./A
./B/A

执行我正在搜索的命令/脚本后,我希望获得剩余的以下层次结构:

./A
./B/A/A
./B/A/B

尝试的解决方案

我尝试使用find-print是占位符):

find \( -path ./A -or -path ./B/A \) -prune -or -print

这不起作用,因为它删除了“请勿触碰”列表中条目的父目录:

$ find \( -path ./A -or -path ./B/A \) -prune -or -print
.
./B
./B/B
./B/C
./C

尤其是,./B当我需要保留时,这会删除它./B/A。哎呀,毕竟它会删除当前目录。

我想避免递归调用(即find -exec something-that-calls-find.sh),因为我要处理的目录列表非常大......

答案1

我认为使用正则表达式来匹配路径是最简单的

  • ./B/A
  • ./B/A/A
  • ./B/A/B
  • ./B/A/B/C
  • 等等

因此,以下内容将匹配文件./A夹下的任何内容./B/A(包括它)。我添加了一个\以使命令更具可读性。另请注意,这仅适用于 GNU find,即不适用于 BSD find

find -depth -regextype posix-extended -mindepth 1 \
! \( -path "./A" -or -regex "\./B(/A(/.*)?)?" \)

解释一下正则表达式:/.*匹配目录下的任何内容A。这里需要斜线,因为否则目录AB也会被匹配。这个先前的模式可以出现零次(对于目录A)或一次(对于下面的任何内容A),这就是为什么我们需要?。由于我们不想删除B,它后面的部分可以出现零次或一次(?)。

由于有一个否定 ( !),因此该find命令将匹配:

./B/B
./B/C
./C

然后,您可以添加-exec rm -rf {} 删除这些文件和文件夹的选项。-depth不过,我们需要从最深层开始的选项,以免尝试删除不再存在的文件夹。

答案2

这是我自己的解决方案。
笔记:当谈到 shell 和实用程序时,我并不是那么注重可移植性,所以它可能严重依赖于 Bash 4 和 GNU find。

代码

#!/bin/bash

## given "a/b/c/d", prints "a/b/c", "a/b" and "a".
# $1...: pathes to process
function get_parent_directories() {
    local CURRENT_CHUNK

    for arg; do
        CURRENT_CHUNK="$arg"

        while true; do
            CURRENT_CHUNK="$(dirname "$arg")"
            [[ "$CURRENT_CHUNK" == "." ]] && break
            echo "$CURRENT_CHUNK"
        done
    done
}

## recursively removes all files in given directory, except given names.
# $1: target directory
# $2...: exceptions
function remove_recursive() {
    local DIR="$1"
    shift
    local EXCEPTIONS=( "$@" )

    # find all files in given directory...
    local FIND_ARGS=( find "$DIR" -mindepth 1 )

    # ...skipping all exceptions and below...
    for file in "${EXCEPTIONS[@]}"; do
        FIND_ARGS+=( -path "$file" -prune -or )
    done

    # ...and ignoring all parent directories of exceptions (to avoid removing "./B" when "./B/A" is an exception)...
    while read file; do
        FIND_ARGS+=( -path "$file" -or )
    done < <(get_parent_directories "${EXCEPTIONS[@]}" | sort -u)

    # ...and printing all remaining names, without their descendants (we're going to recursively remove these anyway).
    FIND_ARGS+=( -print0 -prune )

    "${FIND_ARGS[@]}" | xargs -r0 rm -r
}

解释

生成的find命令行以序列链的形式构建-predicates -actions -or

这意味着:对于每条路径,如果-predicates成功,则执行-actions,否则继续下一个序列。链中的最后一个元素就是-actions,这是默认情况。

-prune这里,我对 中直接找到的所有路径执行此操作$EXCEPTIONS。这样可以阻止find超出这些名称的范围。

接下来,我不会对 中的所有路径的父目录执行任何操作$EXCEPTIONS。我们不想删除异常的父目录,因为删除是递归的。

最后,我将所有剩余的路径(默认情况)输入到xargs rm -r。这比 更快,因为只会生成-exec rm -r {} \;一个。rm

我也为他们做了,因为如果我们要删除的话,-prune明确删除是没有意义的。./A/B/C./A/B

附言:这篇文章最终出现在我的代码片段库中 :)

相关内容