查找仅包含一个特定文件(.DS_Store)而不包含其他文件的文件夹?

查找仅包含一个特定文件(.DS_Store)而不包含其他文件的文件夹?

有没有办法使用findmacOS 终端中的命令来递归查找给定路径内的所有文件夹几乎为空,或者除了单个 .DS_Store 文件之外都是空的?

我在终端尝试了以下操作但无济于事:

find TARGET_PATH -empty -type d

显然,这些超级隐形的 .DS_Store 文件仍被视为文件夹内容即使没有其他文件。 :/

这个问题是关于我试图完成的一项较大任务的一小部分。除了部分脚本似乎只有在我直接从终端运行时才有效之外,我现在几乎已经完成了所有工作。以下是主要问题的链接,以防有人读到这个问题可能会有所帮助:对选定文件夹执行一系列命令的服务,以消除 Finder 中 .DS_Store 文件的问题

答案1

解决方案

以下find命令可以完成我认为您想要的操作:

braces={} find TARGET_PATH -type d \( -empty -o -exec sh -c '
   echo | find "$1" ! \( -type f -name .DS_Store \) -exec sh -c "
      for f do read dummy || { kill -s INT \"\$PPID\"; exit 1; } ; done
   " inner-sh "$braces" +
' outer-sh {} \; \) -print

解释

该命令打印-empty评估结果为 true 或自定义-exec测试评估结果为 true 的目录。自定义测试旨在忽略命名的常规文件.DS_Store,并判断它是否将给定目录视为空目录。

自定义测试是一个sh名为outer-sh。它从主程序中find获取一个目录的路径名。它find以目录作为单个起点运行另一个路径名。此内部程序find旨在忽略名称为的常规文件.DS_Store。它至少找到其起点,可能找到更多文件。它将它们传递给另一个自定义“测试”,即sh名为 的进程inner-sh。这次是-exec … +,因此可能为 提供了多个路径名sh。我将其称为“测试”(带引号),因为它实际上不是 上下文中的测试find;它的工作不是为运行它的 测试某些东西;它的工作是在某些情况下find终止它。find

内部自定义“测试”旨在判断它是否获得了多个位置参数。它可以简单地检查其$#,但内部find可以决定即使两个路径名生成的命令行太长,也可以使用sh一个参数调用,将剩余的路径名(如果有)留给单独的sh进程。这里有一个技巧echo

的诀窍echo是将这个 soleecho通过管道传输到内部find。Soleecho只打印一个空行(即一个换行符)。内部find不会从其标准输入读取,但我们的内部sh“测试”可能会;而且它们确实会。对于每个路径名read dummy都执行。第一个路径名是内部的起点findread dummy将使用来自 的行echo,它将成功。第二个路径名可能会提供,也可能不会提供(取决于目录在忽略可能的 后是否为非空或为空.DS_Store);如果提供了,那么将会有另一个read dummy并且它将失败(无论是否由sh与第一个相同的程序执行;这就是诀窍)。

如果read dummy失败,则执行内部“测试”的 shell 将终止其父级并退出。父级是内部的find。终止它会使其停止处理文件并生成更多shs,即使它本来会这样做。

换句话说,如果目录中有除此以外的文件,.DS_Store那么将会read dummy失败,这将导致终止内部文件,因此外部看到的find内部文件的退出状态将为非零。findsh

如果内部在注意到信号之前find注意到内部的消亡sh,并且如果它没有其他事情要做,它可能会自行退出。内部在之后sh以失败()退出,因此即使如此,内部也会以非零退出状态退出(作为测试始终为真,但如果实用程序失败,则应返回非零退出状态)。我怀疑这种在被杀死之前(当它将被杀死时)退出的场景是否可能,我想它应该首先对信号做出反应。以防万一我错了。exit 1killfind-exec utility … +findfindexit 1

因此,如果目录中有除 之外的文件.DS_Store,外部sh将看到内部find失败,它本身将以非零退出状态退出。外部将解释find为自定义测试评估为 false,因此-print不会执行。

如果目录中没有除 之外的文件,.DS_Store则将只有一个 innersh和一个read dummy。没有kill。几件事将按顺序成功:read,inner sh,inner find,outer sh;然后 outerfind会认为自定义测试评估为真,因此-print将执行 。


笔记

  • 我故意将所有内容都保留在主内容中find(而不是例如find … | awk …),因此现在您可以在之后、之前或代替添加一些内容( -exec …,, ...) 。-delete-print

  • 关于braces={}。如果我{}在内部find … -exec …期望的地方使用它,它可能会扩展为 find(取决于实现)并完全破坏代码。通过将其存储{}为环境中的变量并让正确的 shell 对其进行扩展,我将其{}从外部隐藏起来find

  • 自定义测试对每个测试目录运行多个进程。不要指望整个命令的执行效果与 一样好find TARGET_PATH -type d -empty

  • 您使用了-empty,所以我知道您的find支持它。一般来说-empty不是可移植的操作数。我们的自定义测试允许您-empty -o从代码中删除(然后最外层的\( \)对就不需要了,但它可能会保留)。自定义测试本身会检测真正空的目录。这意味着您可以将解决方案与find不支持的实现一起使用-empty。尽管如此,由于-o工作原理(如果返回 true 则-test1 -o -test2跳过),如果您可以使用,那么使用它,因为对于真正空的目录,它可以加快速度。-test2-test1-empty

  • 在内部,find我可以使用-exec … \;而不是。一个两难的局面:当最多两个​​路径名就足够时,-exec … +是使用+并提前find查询文件系统以构建一个可能很大的列表提供给内部更好?还是使用并经常产生两个内部进程更好,但不需要提前从文件系统读取(并且没有循环)? 技巧允许我们使用并且您可以测试哪种变体更适合您。在我的测试中,在一个相当大的目录树上总体上速度稍快,但您的里程可能会有所不同。sh\;shforecho\;+

    如果您需要将该解决方案与一些find不支持的旧解决方案一起使用-exec … +,请自由转换-exec … \;

  • 要判断目录是否为空,-empty需要对该目录具有读取权限。如果不允许读取,因此-empty无法知道目录是否为空,则它将假定非空并评估为 false。据我测试,我们的自定义测试以相同的方式运行;因此,无论您是否使用它,如果您可以告诉它忽略文件-empty -o,您都应该获得与从 获得的结果相同的结果。-empty.DS_Store

    不同之处在于我们的代码会打印(到 stderr)permission denied每个目录的更多“副本”,而无需读取权限。我不认为这会成为问题。

  • 要判断目录是否为空,-empty不需要对该目录具有执行权限。我特意设计了不带cd和 的解决方案-execdir,因此我们的自定义测试都不需要执行权限。

  • 更改-empty -o! -empty,您将发现包含孤立.DS_Store文件的目录,但不是真正空的目录。

相关内容