比较两个文件的修改日期

比较两个文件的修改日期

我正在创建一个通用编译/转译系统。了解文件是否已编译/转换的一种方法是比较源文件和目标文件的修改日期。

我需要编写一个 bash 脚本来做到这一点:

source_file=foo;
target_file=bar;

stat_source=$(stat source_file);
stat_target=$(stat target_file);

但如何从统计输出中提取日期并进行比较?有没有比stat比较文件最近修改时间更好的方法呢?

如果我在日志文件上调用 stat,我会得到以下信息:

16777220 12391188 -rw-r--r-- 1 alexamil staff 0 321 "Jun 22 17:45:53 2017" "Jun 22 17:20:51 2017" "Jun 22 17:20:51 2017" "Jun 22 15:40:19 2017" 4096 8 0 test.log

AFAICT,时间粒度不小于秒。如果可能的话,我需要得到比这更细粒度的东西。

答案1

鉴于您正在使用stat(类似的功能,但 BSD 和 GNU 上的输出格式不同),您还可以使用该test实用程序,它直接进行比较:

   FILE1 -nt FILE2
          FILE1 is newer (modification date) than FILE2

   FILE1 -ot FILE2
          FILE1 is older than FILE2

在你的例子中,

if [ "$source_file" -nt "$target_file" ]
then
    printf '%s\n' "$source_file is newer than $target_file"
fi

该功能在 POSIX 中不可用(请参阅其的文档test),其理由如下:

新发明的或来自 KornShell 的一些附加初选作为条件命令 ([[]]) 的一部分出现在早期提案中: s1 >s2, s1 <s2, str = pattern, str != pattern, f1-ntf2, f1-otf2 和 f1 -ef f2。当从 shell 中删除条件命令时,它们不会被转移到测试实用程序中,因为它们尚未包含在 sh 实用程序的历史实现中内置的测试实用程序中。

不过,这在未来可能会改变因为该功能得到了广泛支持。

请注意,当操作数是符号链接时,会考虑符号链接目标的修改时间(这通常是您想要的,find -newer如果不需要,请使用它)。当无法解析符号链接时,实现之间的行为(有些人认为现有文件总是比无法解析的文件更新,有些人总是报告错误的如果任何操作数无法解析)。

另请注意,并非所有实现都支持亚秒粒度(例如,从版本 4.4 开始,bash's test/ builtin 仍然不支持,而 GNU和或的内置函数则支持,至少在 GNU/Linux 上)。[testtestzshksh93

以供参考:

  • 对于 GNUtest实用程序实现(尽管请注意,您的 shell,如果fish或类似 Bourne,也将具有一个test/[内置命令,通常会隐藏它,请使用env test而不是test绕过它),获取时间在 test.c 中读取struct timespec,并且
  • 选项-nt使用该数据

答案2

在这个linux系统上进行测试。测试文件时间的常用方法是 shell:

[ file1 -nt file2 ] && echo "yes"

似乎可以用秒来完成。这将以小于一秒的时间差接触文件,不会检测到该差异:

$ touch file2; sleep 0.1; touch file1; [ file1 -nt file2 ] && echo "yes"

要确认问题(点后的时间为纳秒):

$ ls --time-style=full-iso -l file?
-rw-r--r-- 1 user user 0 2017-06-23 01:37:01.707387495 -0400 file1
-rw-r--r-- 1 user user 0 2017-06-23 01:37:01.599392538 -0400 file2

file1比 . 稍微新一些file2

现在的问题是如何正确处理时间值。

一种解决方案是使用 ls 的格式化输出:

$ ls --time-style=+%s.%N -l file?
-rw-r--r-- 1 user user 0 1498196221.707387495 file1
-rw-r--r-- 1 user user 0 1498196221.599392538 file2

将时间提取到两个变量(不带点):

$ file1time=$(ls --time-style=+%s%N -l file1 | awk "{print(\$6)}")
$ file2time=$(ls --time-style=+%s%N -l file2 | awk "{print(\$6)}")

并比较时间(纳秒的时间勉强适合 64 位值。如果您的系统不使用 64 位,则此比较将失败):

$ [ $file1time -gt $file2time ] && echo "yes"
yes

这表明它file1file2


如果ls无法获得所需的格式,那么您可以尝试 stat。

$ stat file1
  File: file1
  Size: 0               Blocks: 0          IO Block: 4096   regular file
Device: 805h/2053d      Inode: 9180838     Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/    user)   Gid: ( 1000/    user)
Access: 2017-06-23 01:37:01.707387495 -0400
Modify: 2017-06-23 01:37:01.707387495 -0400
Change: 2017-06-23 01:37:01.707387495 -0400
 Birth: -

如果输出显示纳秒,我们将需要日期来解析(和格式化)时间。

$ stat --printf='%y\n' file1
2017-06-23 01:37:01.707387495 -0400

$ date +'%s%N' -d "$(stat --printf='%y\n' file1)" 
1498196221707387495

其余的都是一样的,将file1和file2的结果赋值给两个变量,并进行数值比较。

答案3

如果您愿意使用非嵌入式 Linux,那么您可以使用test外部命令,它是 GNU coreutils 的一部分。 (test是大多数 shell 的另一个名称[,并且是内置的)。它具有纳秒粒度(达到文件系统报告的精度)。

/usr/bin/test "$target" -nt "$source"

-nt运算符不是由 POSIX 定义的,但它存在于许多实现中,包括 dash、bash、pdksh、mksh、ATT ksh、zsh、GNU coreutilstest和 BusyBox。然而,许多实现(dash、bash、pdksh、mksh、BusyBox - 在 Debian jessie 上测试)仅支持 1 秒粒度。

但使用专用于这项工作的工具会是一个更好的主意,例如制作。仅当某个文件比其他文件新时才运行命令是 make 的全部意义。在名为的文件中包含以下内容Makefile(请注意,每个命令行之前都需要一个制表符)。

target: source
    echo This command is only executed if target is newer than source
    do_stuff <source >$@

运行make target以执行生成它的命令。如果target存在并且比 更新source,则不会执行命令。阅读 make 的一些文档以获取更多信息。

答案4

POSIXly,你会使用find

if find "$source_file" -prune -newer "$target_file" | grep -q '^'; then
  printf '%s\n' "$source_file is newer than $target_file"
else
  echo "It's not newer or one of the files is not accessible"
fi

对于符号链接,它比较符号链接本身的运行时间。要比较符号链接的目标,请添加-H-L选项。

这假设$source_file不以其中一个谓词开头-,也不对应于其中一个find谓词。如果您需要处理任意文件名,您需要首先执行以下操作:

case $source_file in
  (["-+()!"]*) source_file=./$source_file;;
esac

GNU 和 FreeBSDfind实现至少支持亚秒级粒度。 AFAICT,macos 似乎至少没有在 HFS+ 文件系统的文件属性中存储亚秒级信息。

相关内容