有没有办法使用find
macOS 终端中的命令来递归查找给定路径内的所有文件夹几乎为空,或者除了单个 .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
都执行。第一个路径名是内部的起点find
,read dummy
将使用来自 的行echo
,它将成功。第二个路径名可能会提供,也可能不会提供(取决于目录在忽略可能的 后是否为非空或为空.DS_Store
);如果提供了,那么将会有另一个read dummy
并且它将失败(无论是否由sh
与第一个相同的程序执行;这就是诀窍)。
如果read dummy
失败,则执行内部“测试”的 shell 将终止其父级并退出。父级是内部的find
。终止它会使其停止处理文件并生成更多sh
s,即使它本来会这样做。
换句话说,如果目录中有除此以外的文件,.DS_Store
那么将会read dummy
失败,这将导致终止内部文件,因此外部看到的find
内部文件的退出状态将为非零。find
sh
如果内部在注意到信号之前find
注意到内部的消亡sh
,并且如果它没有其他事情要做,它可能会自行退出。内部在之后sh
以失败()退出,因此即使如此,内部也会以非零退出状态退出(作为测试始终为真,但如果实用程序失败,则应返回非零退出状态)。我怀疑这种在被杀死之前(当它将被杀死时)退出的场景是否可能,我想它应该首先对信号做出反应。以防万一我错了。exit 1
kill
find
-exec utility … +
find
find
exit 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
\;
sh
for
echo
\;
+
如果您需要将该解决方案与一些
find
不支持的旧解决方案一起使用-exec … +
,请自由转换-exec … \;
。要判断目录是否为空,
-empty
需要对该目录具有读取权限。如果不允许读取,因此-empty
无法知道目录是否为空,则它将假定非空并评估为 false。据我测试,我们的自定义测试以相同的方式运行;因此,无论您是否使用它,如果您可以告诉它忽略文件-empty -o
,您都应该获得与从 获得的结果相同的结果。-empty
.DS_Store
不同之处在于我们的代码会打印(到 stderr)
permission denied
每个目录的更多“副本”,而无需读取权限。我不认为这会成为问题。要判断目录是否为空,
-empty
不需要对该目录具有执行权限。我特意设计了不带cd
和 的解决方案-execdir
,因此我们的自定义测试都不需要执行权限。更改
-empty -o
为! -empty
,您将发现包含孤立.DS_Store
文件的目录,但不是真正空的目录。