问题
我有一个目录结构,例如像这样(这里尾随斜杠代表文件夹):
./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
附言:这篇文章最终出现在我的代码片段库中 :)