前言

有一个奇怪的需求:我想要删除一个文件夹的全部内容,但保留特定的文件/文件夹;又或者说,清空文件夹的内容,但保留白名单里的文件/文件夹。

在网上能找到一些实现,但大多数都是针对特定问题的特定解,唯一比较通用的是类似 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

可以看到,除白名单内容外,其它都被删了个干净。