删除多个级别中除指定文件/目录之外的所有文件/目录

删除多个级别中除指定文件/目录之外的所有文件/目录

是否可以用一行完成以下操作?

shopt -s extglob

rm -r !(folder1|file1|folder2)
rm -r folder2/!(subfolder)
rm -r folder2/subfolder/!(onefile)

例如,像这样的事情?

rm -r !(folder1|file1|folder2/subfolder/onefile)

答案1

您可以使用-path该工具的(否定)测试find。如果该工具支持-delete,那么它相对简单:

find . ! -path ./folder1 ! -path './folder1/*' ! -path ./file1 ! -path ./folder2/subfolder/onefile -delete

如果onefile存在subfolder则会folder2报错cannot delete: Directory not empty。这将使他们生存,但退出状态find不会0

如果您find支持,-empty那么您可以在目录之前测试目录是否为空-delete。一般来说,您不想测试非目录是否为空。这使命令变得复杂,但0如果一切按计划进行,则允许命令返回。像这样:

find . ! -path ./folder1 \
       ! -path './folder1/*' \
       ! -path ./file1 \
       ! -path ./folder2/subfolder/onefile \
       \( ! -type d -o -empty \) \
       -delete

find有足够的钱来支持-delete并且-empty可能会支持-regex。我的 Debian 中的GNUfind可以做到这一点:

find . ! -regex '\./folder1/*.*\|\./file1\|\./folder2/subfolder/onefile' -delete

笔记:

  • onefile这是阻止find删除subfolder或的存在folder2。您需要更多模式(见下文)或更复杂的正则表达式来保留这些目录,无论是否onefile存在。具有更复杂正则表达式的变体:

    find . ! -regex '\./folder1/*.*\|\./file1\|\./folder2\(/subfolder\(/onefile\)?\)?' -delete
    

    作为奖励,这种改进解决了“非零退出状态”问题。

  • -path使用类似 glob 的模式匹配表示法(?匹配单个字符并*匹配零个或多个字符);-regex使用正则表达式(.匹配单个字符并.*匹配零个或多个字符)。

  • -path-regex匹配文件的整个路径。要设计有效的模式,您需要知道find将使用什么路径。提示:

    • find对于任何不是起点的路径,有义务不使用尾部斜杠。这就是我在 ( ) 中-path./folder1和 文件使用单独模式的原因./folder1/*
    • 另一方面,起点按规定使用。如果起点是 ,那么除了自身的路径之外,.所有路径都将以 开头,那么它将只是。如果起点是则所有路径都将以 开头。./..././
  • 在我的测试中,-delete静默处理.,不会删除,也不会出现错误,因此无需排除..另一方面,它会抛出一个错误./。我不确定这种行为有多可靠。如果有任何疑问,请明确排除.,特别是如果您正在寻求有意义的退出状态。

  • 正则表达式类型很少。例如我-regextype posix-extended之前可以使用! -regex.以下是我想出的最紧凑的命令(虽然不可移植):

    find . -regextype posix-extended \
         ! -regex './folder1(/.*)?|./file1|./folder2(/subfolder(/onefile)?)?' \
           -delete
    

    我故意使用.(通配符)而不是\.(文字点)作为前导..由于起点是.,因此每条路径都.肯定以 开头,因此通配符除了 之外不会匹配任何其他内容.\.如果您更喜欢形式上的正确性,请使用。

  • 如果您想确保不会删除太多内容,可以“试运行”该命令。更改-delete-print,您将看到要删除的内容。这是第一个这样修改的命令:

    find . ! -path ./folder1 ! -path './folder1/*' ! -path ./file1 ! -path ./folder2/subfolder/onefile -print
    

    或者否定所有先前的测试作为一个整体(这与单独否定它们不同,除非您知道还需要更改什么;比较)并更改-delete-print看看什么会幸存:

    find . ! \( ! -path ./folder1 ! -path './folder1/*' ! -path ./file1 ! -path ./folder2/subfolder/onefile \) -print
    
  • 不加引号./folder1/*将是一个错误:shell 会尝试扩展*.


-pathPOSIX 要求但是-delete-regex不是-empty。有时-exec rm {} +可以使用 代替-delete,但这样rm不会删除目录,您需要rmdir.是的,rm -r可以删除目录,但随后rm -r ./folder2也会删除./folder2/subfolder/onefile并违背整个要点。

-delete我们的基本方法仍然可以在没有(并且有所改进)的情况下实现:

find . -depth \
     ! -path  . \
     ! -path  ./folder1 \
     ! -path './folder1/*' \
     ! -path  ./file1 \
     ! -path  ./folder2 \
     ! -path  ./folder2/subfolder \
     ! -path  ./folder2/subfolder/onefile \
       \( \( ! -type d -exec rm {} + \) -o \( -type d -exec rmdir {} + \) \)

通过显式排除./folder2./folder2/subfolder我使命令返回有意义的退出状态并保留这些目录,即使这些目录onefile不存在。

如果您想删除onefile不存在的目录,则不要使用额外的模式。研究--ignore-fail-on-non-empty的选项rmdir。 POSIX 不需要此选项。

我不会说最后一个命令很紧凑。我喜欢那个-regex更好的。

答案2

对于这样一个潜在的破坏性操作,我会手动小心地完成:获取所有文件的列表,以及完整路径;编辑掉应该保留的文件(grep -v,或使用您最喜欢的文本编辑器);检查是否有任何遗漏;将列表提供给(如果列表很大,rm可能需要使用)。xargs(1)请小心包含空格或其他不寻常字符的文件名!

相关内容