前言
有一个奇怪的需求:我想要删除一个文件夹的全部内容,但保留特定的文件/文件夹;又或者说,清空文件夹的内容,但保留白名单里的文件/文件夹。
在网上能找到一些实现,但大多数都是针对特定问题的特定解,唯一比较通用的是类似 rm !(a|b)
这样的写法,但是它依赖 bash 拓展。
于是打算让 GPT 写一个,但是发现 GPT 并不能处理好这个任务,写出来的东西要么逻辑不通,要么没有处理好父文件夹的问题,导致白名单里的东西照样被删除,而且经过多次提醒仍然没法领悟到问题所在。
于是基于 GPT 写的东西人工改了一下,就有了这篇文章,希望 GPT 继续努力 :)
脚本
#!/bin/bash
# 要清理的目标目录
target_dir="./A"
# 要保留的目录/文件白名单
keep=(
"./A/B/C"
"./A/E.txt"
)
# 根据 keep ,生成要保护的父路径列表,装在 extra_keep 中
# 比如 keep 中写明了 ./A/B/C ,那就得额外确保 ./A/B 和 ./A 不会被删除,因为它们也是会出现在 find 的结果里的
# 不知怎么的,GPT 就是考虑不到这点
add_parent_path() {
local path="$1"
# target_dir 不在这里保护,其由最下面的 -mindepth 1 保护
while [ "$path" != "$target_dir" ]; do
extra_keep+=("$path")
# 去掉路径的最后一段,得到父路径
path=$(dirname "$path")
done
}
for item in "${keep[@]}"; do
add_parent_path "$item"
done
# 构建 find 命令的排除参数
# 对于 keep ,既要保护文件/目录本身,又要保护其子目录(如果有的话)
# 比如 keep 中写明了 ./A/B ,那 ./A/B/C 就不能被删除掉
exclude_params=()
for item in "${keep[@]}"; do
# 这里的 -prune 起到了排除子目录的效果
exclude_params+=(-path "$item" -prune -o)
done
# 对于 extra_keep ,只需要保护目录本身不被删除即可,无需保护子目录
for item in "${extra_keep[@]}"; do
exclude_params+=(-path "$item" -o)
done
# 打印被删除的文件/目录列表
find "$target_dir" -mindepth 1 "${exclude_params[@]}" -exec echo '{}' +
# 利用 find 命令执行删除
# -mindepth 1 是必要的,因为 extra_keep 并不会保护到 target_dir ,它被删了就前功尽弃了
find "$target_dir" -mindepth 1 "${exclude_params[@]}" -exec rm -rf '{}' +
效果
以上面的脚本为例。
运行前:
tree A
A
├── B
│ ├── C
│ │ └── D.txt
│ └── G
├── E.txt
├── F.txt
└── H
└── I.txt
4 directories, 4 files
运行后:
tree A
A
├── B
│ └── C
│ └── D.txt
└── E.txt
2 directories, 2 files
可以看到,除白名单内容外,其它都被删了个干净。