5

I want to check whether link lnkfile is older than a regular reference file reffile.

The bash test builtin dereferences links, so test lnkfile -ot reffile compares the target of lnkfile, not the link itself.

Is there a way to make the test builtin not follow symlinks? Otherwise, how can I compare the time of a symlink?

2

I don't think there is a way with test, but you could use find.

test "$(find reffile -prune -newer lnkfile)" && command

Here, find returns output if lnkfile is older than reffile. test without an option is equivalent to test -n. This is true if the length of the string is nonzero. Hence, if there is output from find, command is executed.


In the comments, I was asked to make a comparison of this solution vs. stat. Firstly, I find the stat approach perfectly fine.

I did a benchmark to compare. I repeated the test a few times, alternating, and got similar results.

$ time (for i in {1..1000}; do test "$(stat --format=%Z a)" -lt "$(stat --format=%Z b)" && echo foo > /dev/null ; done)

================
CPU 101%
CPU 104%
user    1.264
system  0.942
total   2.108

$ time (for i in {1..1000}; do test "$(find b -newer a)" && echo foo > /dev/null ; done)

================
CPU 104%
user    0.693
system  0.526
total   1.164

I looks like find is almost twice as fast, perhaps because it's a single process rather than two stats? I'm not sure how else to compare them; please comment if there are other relevant aspects you can think of.

Here are some other differences, as per Stéphane Chazelas's comments below:

Other differences are: the find one is standard. while the stat one needs the GNU implementation of stat. The stat one won't work for files modified within the same second (while find should work on systems where sub-second granularity for timestamps is supported). Neither find nor stat support arbitrary file names.

You'll find other differences if either of the files cannot be stat()ed.

  • What test implementation do you have that does not have -n? It's a standard option... Also quote the command substitution. – Kusalananda Jan 21 at 22:50
  • @Kusalananda I'm just confirming that no options is identical to -n, so it's easier to grep the man. Good point with the quote. Thanks. – Sparhawk Jan 21 at 22:56
  • I misunderstood what you were saying about -n and read it as "there is no -n option" instead of "test without an option is the same as test -n". – Kusalananda Jan 22 at 0:13
  • 1
    @Kusalananda Ah got it. I edited to clarify my ambiguous language. Thanks again. – Sparhawk Jan 22 at 1:25
  • 1
    Thank you, I'm convinced. It's striking to see how the process calls dominate the total overhead of these loops! – L. Levrel Jan 22 at 13:21
3

As far as I know, bash doesn't offer any versions of -ot (and the like) which avoid dereferencing symlinks.

What you can do instead is use GNU stat (which doesn't dereference symbolic links without -L) and compare their mtime epochs numerically:

if (( "$(stat --format=%Z lnkfile)" < "$(stat --format=%Z reffile)" )); then
    # lnkfile is older
fi
  • Thank you for answering. Now I'm well embarrassed with two valid answers: what do you think about Sparhawk's solution? Especially in re efficiency, since I'll have scores of link-and-file pairs to test. – L. Levrel Jan 22 at 8:50
1

With zsh 5.6 or above (for the nanosecond precision), you could do it with builtins only with:

zmodload zsh/stat
if 
  stat -LA lnktime -F '%021s%N' +mtime -- $lnkfile &&
    stat -A reftime -F '%021s%N' +mtime -- $reffile &&
    [[ $lnktime < $reftime ]]
then
  print -r -- $lnkfile is older than $reffile
fi

Which would work regardless of what characters or non-characters the file names contain and compare timestamps down to the nanosecond.

We're comparing the timestamps as strings (number of nanoseconds as a decimal string representation zero-padded to 30 digits) instead of floating points because the typical double precision floating points of a x86_64 PC running GNU/Linux at least don't have enough precision to store numbers like 1548195897.775033155 so you would not be able to tell apart two files modified today within the same 100 nanoseconds.

$ ((1548195897.775033155 < 1548195897.775033255)) && echo yes
$ [[ 1548195897775033155 < 1548195897775033255 ]] && echo yes
yes
  • Thank you for the details. Could you expand about "non-characters" please? I've tested with a filename containing newline: GNU stat shows no problem. find will replace the newline with ? if output goes to the terminal, otherwise it is preserved (which is the case in Sparhawk's answer, but anyway we're just testing if there's any output, so ? would be fine). All substitutions are double-quoted to avoid word splitting. – L. Levrel Jan 23 at 10:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.