当我部署软件时,我会将一个压缩文件发送到目标服务器并提取其内容。除此之外,我还会在目录中放置一个元数据文件,详细说明已部署的内容。
如果我想查找自部署软件以来发生过更改的任何文件,我可以简单地找到比元数据文件有新的修改时间的文件:
find . -newer deployment_metadata.txt
这很好,很直接。
现在,我还想找到比部署元数据文件更旧的文件。有人认为您可以使用感叹号来否定“较新”检查
find . ! -newer deployment_metadata.txt
但是“不新”并不完全等同于“较旧”,因为任何具有相同时间戳的文件也“不新” - 所以该命令还包括我刚刚部署的所有文件!
因此,我想知道在查找(严格的)旧文件时我是否错过了什么技巧?
我当前的解决方案是创建一个新文件(在临时目录中),touch
其修改时间为部署元数据.txt文件之前一分钟。然后我可以使用以下参数:! -newer /var/tmp/metadata_minus_1
。
这可行,但似乎浪费时间必须创建然后清理临时目录中的文件 - 特别是因为不同的用户可能使用我的脚本来检查这一点(不想出现文件所有权问题,所以我实际上将其附加${USER}
到文件名)。
答案1
一种方法是(滥用)使用纪元时间。下面是一次测试运行,我首先在一个空目录中按顺序创建了七个文件,就文件而言,这些c#
文件是“相同的” :ctime
find
$ 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
:
ctime
>c2
ctime
≤c2
ctime
≥c2
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
为退出状态0
或1
,这将成为的退出状态sh
;然后-exec
外部的find
分别评估为真或假。我们想知道结果字符串是否为空,它可以是空,也可以是参考文件的名称(
deployment_metadata.txt
在我们的例子中)。如果参考文件的名称包含换行符仅有的,请记住$(…)
删除所有尾随换行符;在这种情况下,我们的测试将无法区分这两种可能性。要解决这个问题,您应该提供更宽的路径,例如./…
。只有当基本名称只包含换行符时才会出现此问题;实际上您不需要关心,除非您故意使用这样的名称代替deployment_metadata.txt
。在 中
[
,-n
是默认值。我-n
明确使用了 以防万一参考文件的名称以 开头-
,并且可以被解释为[
选项。关键是 的每个实现都[
不够聪明,无法通过 之前只有一个参数这一事实提前识别默认情况]
。参考文件的名称是deployment_metadata.txt
并且无论如何都会保存,但如果您想使用-whatever
则显式-n
它仍然应该有效。我们需要一个 shell 来工作
$(…)
。每个通过原始测试的文件都会有一个sh
shellfind
。创建新进程的速度相对较慢,因此该解决方案的性能可能较差。我们的
-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
可能可以作为进一步的测试,但由于它可能涉及生成其他进程,因此会违背目的。