查找(严格)早于另一个文件的文件

查找(严格)早于另一个文件的文件

当我部署软件时,我会将一个压缩文件发送到目标服务器并提取其内容。除此之外,我还会在目录中放置一个元数据文件,详细说明已部署的内容。

如果我想查找自部署软件以来发生过更改的任何文件,我可以简单地找到比元数据文件有新的修改时间的文件:

find . -newer deployment_metadata.txt

这很好,很直接。

现在,我还想找到比部署元数据文件更旧的文件。有人认为您可以使用感叹号来否定“较新”检查

find . ! -newer deployment_metadata.txt

但是“不新”并不完全等同于“较旧”,因为任何具有相同时间戳的文件也“不新” - 所以该命令还包括我刚刚部署的所有文件!

因此,我想知道在查找(严格的)旧文件时我是否错过了什么技巧?

我当前的解决方案是创建一个新文件(在临时目录中),touch其修改时间为部署元数据.txt文件之前一分钟。然后我可以使用以下参数:! -newer /var/tmp/metadata_minus_1

这可行,但似乎浪费时间必须创建然后清理临时目录中的文件 - 特别是因为不同的用户可能使用我的脚本来检查这一点(不想出现文件所有权问题,所以我实际上将其附加${USER}到文件名)。

答案1

一种方法是(滥用)使用纪元时间。下面是一次测试运行,我首先在一个空目录中按顺序创建了七个文件,就文件而言,这些c#文件是“相同的” :ctimefind

$ for i in a b "c1 c2 c3" d e; do touch $i; sleep 1; done
$ find -newer c2
.
./d
./e
$ find -not -newer c2
./c3
./c2
./a
./b
./c1
$ find -newerct @$(($(stat -c %Z c2)-1))
.
./c3
./d
./c2
./e
./c1
$ find -not -newerct @$(($(stat -c %Z c2)-1))
./a
./b

ctime这应该代表相对于的所有可能集合c2

  1. ctime>c2
  2. ctimec2
  3. ctimec2
  4. ctime<c2

至少匹配得有些模糊。

第三个命令获取ctime文件 的纪元c2,通过 shell 算法减 1,并将其作为引用提供给-newerct@需要 来find将 解释为这样的时间戳),以查找ctime比此解释的时间戳更新的所有文件(请-newerXY参阅)。第四个命令否定了此匹配,并且实际上应该会执行您想要的操作,如果我正确理解了这个问题,如果您像我的示例man find一样放置参考文件。c2

请注意,“1 秒”偏移量有点随意(这就是我所说的“模糊匹配”),可以想象出可以构造错误的情况。但是,文件的时间戳无论如何都不是“确定的”,也不能相信它是确定的,所以我无法想象它在实际情况下会产生安全问题或实际问题。

实际上,在实践中你甚至可能想要增加1 秒的偏移量(我在您的问题中看到您现在使用 1 分钟),但这是一个实现细节。

答案2

也许将find输出放入命令循环中test,这样您就可以使用“旧于”测试:

find ... | while read file;
do
  [ "$file" -ot deployment_metadata.txt ] && echo "$file"
done

答案3

我想找到所有比现有文件更旧的文件,并遵循可接受的解决方案:

find . -type f -not -newer spec-file

结果中包含“spec-file”,因此删除文件结果不正确。

我使用以下命令查找要删除的最新文件:

find . -type f -not -newer spec-file -printf '%T+  %p\n' | sort | tail -3

然后使用倒数第二个文件删除它们:

find . -type f -not -newer result-file -exec rm {} +

答案4

初步说明

这个答案的目的是提供严格且可移植的代码(为了比较:find … -newerct …来自这个答案以及[ … -ot …来自这个答案不可移植)。


基本解决方案

对于每个通过的文件! -newer deployment_metadata.txt,检查deployment_metadata.txt是否-newer

find . ! -newer deployment_metadata.txt -exec sh -c '
   [ -n "$(find deployment_metadata.txt -prune -newer "$1")" ]
' find-sh {} \; -print

笔记

  • find-sh解释如下:中的第二个 sh 是什么sh -c 'some shell code' sh

  • -prune如果是deployment_metadata.txt目录的话。

  • [ -n "$(find …)" ]将内部的非空或空输出分别转换find为退出状态01,这将成为的退出状态sh;然后-exec外部的find分别评估为真或假。

  • 我们想知道结果字符串是否为空,它可以是空,也可以是参考文件的名称(deployment_metadata.txt在我们的例子中)。如果参考文件的名称包含换行符仅有的,请记住$(…)删除所有尾随换行符;在这种情况下,我们的测试将无法区分这两种可能性。要解决这个问题,您应该提供更宽的路径,例如./…。只有当基本名称只包含换行符时才会出现此问题;实际上您不需要关心,除非您故意使用这样的名称代替deployment_metadata.txt

  • 在 中[-n是默认值。我-n明确使用了 以防万一参考文件的名称以 开头-,并且可以被解释为[选项。关键是 的每个实现都[不够聪明,无法通过 之前只有一个参数这一事实提前识别默认情况]。参考文件的名称是deployment_metadata.txt并且无论如何都会保存,但如果您想使用-whatever则显式-n它仍然应该有效。

  • 我们需要一个 shell 来工作$(…)。每个通过原始测试的文件都会有一个shshell find。创建新进程的速度相对较慢,因此该解决方案的性能可能较差。

  • 我们的-exec足以测试我们想要的内容。这种方法! -newer deployment_metadata.txt并非绝对必要。但它很有用,可以提高性能。每次! -newer评估为 false 时,我们的 costly-exec都不会被评估。如果没有这项初步测试,-exec将对外部测试的每个文件进行评估find

  • -exec您可以在我们的 之前或/和 之前直接(或代替)添加更多测试/操作-print。我们的整体-exec … \;相当于-older deployment_metadata.txt您想要的假设。这就是 的美妙之处find-exec您可以使用 构建几乎任何测试。


可能的改进

如果你只想要-print结果,可以稍微优化一下方法:

find . ! -newer deployment_metadata.txt -exec sh -c '
   for f do
      [ -n "$(find deployment_metadata.txt -prune -newer "$f")" ] \
      && printf "%s\n" "$f"
   done
' find-sh {} +

在这种方法中,我们sh将为外部提供的多个路径名提供服务find(仍然find可以按顺序运行多个sh进程,以防所有要提供的路径名都会触发argument list too long错误,find这很聪明)。这样我们就可以减少进程数sh。我们添加了printf,但printf几乎在 的任何实现中都是内置的sh,因此它作为sh进程的一部分运行,而不是单独的进程。

您仍然可以find在 之前添加更多 - 特定测试-exec,但不能在 之后添加(好吧,从技术上讲您可以这样做,但这不会按预期工作,因为-exec … +始终评估为真)。在 之前(或代替 )正确编写的 shell 代码printf可能可以作为进一步的测试,但由于它可能涉及生成其他进程,因此会违背目的。

相关内容