The built-in test and [ utilities have the -nt ("newer than") and -ot ("older than") tests in most shells, even when the shell is running in "POSIX mode" (also true for the external utilities of the same names on the systems that I have access to). These tests are for comparing modification timestamps on two files. Their documented semantics are slightly varying across implementations (with regards to what happens if one or the other file exists on not), but they are not included in the POSIX spec. for the test utility.

They were not carried forward into the test utility when the conditional command was removed from the [KornShell] shell because they have not been included in the test utility built into historical implementations of the sh utility.

Assuming I'd like to compare the modification timestamp between files in a /bin/sh shell script and then take action depending on whether one file is newer than the other, as in

if [ "$sigfile"     -nt "$timestamp" ] ||
   [ "$sigfile.tmp" -nt "$timestamp" ]
then
    return
fi

... what other utility could I use, apart from make (which would make the rest of the script unwieldy to say the least)? Or should I just assume that nobody is ever going to run the script on a "historical implementation of sh", or resign to writing for a specific shell like bash?

  • As you can see in my answer, bash does not implement the -nt feature of test in a way that is expected since approx. 1995. You should use the find basd expression even with bash if you like correct behavior. – schily Jun 14 at 8:39
up vote 9 down vote accepted

POSIXLY:

f1=/path/to/file_1
f2=/path/to/file_2

if [ -n "$(find -L "$f1" -prune -newer "$f2")" ]; then
    printf '%s is newer than %s\n' "$f1" "$f2"
fi

Using absolute path to files prevent a false positive with filename contains newlines only.

In case of using relative path, then change find command to:

find -L "$f1" -prune -newer "$f2" -exec echo . \;
  • technically, I think it fails if $f1 only contains newlines ;) – ilkkachu Jun 14 at 7:28
  • 1
    @ilkkachu could be worked around with -exec echo x \; or similar? – muru Jun 14 at 7:33
  • @muru, yeah. -printf would be easy if it were standard – ilkkachu Jun 14 at 7:36
  • 3
    @Kusalananda AFAICT, find's exit status is independent of what the individual tests of find return - except perhaps if there's an error in actually running those tests. find exits 0 even if no files are found. – muru Jun 14 at 7:46
  • 1
    Note that the behaviour of [ / -nt /nofile ] varies with the implementation (but never outputs any error). – Stéphane Chazelas Jun 14 at 9:04

This could be a case for using one of the oldest Unix command, ls.

x=$(ls -tdL -- "$a" "$b")
[ "$x" = "$a
$b" ]

The result is true if a is newer than b.

  • 3
    That doesn't work if file names start with - (missing --). You'd need -L for it to be equivalent to -nt. That doesn't work to compare x and $'x\nx' for instance. – Stéphane Chazelas Jun 14 at 8:47
  • I added those, thanks. – meuh Jun 14 at 8:55
  • 1
    Eek! But also huh. – Kusalananda Jun 14 at 10:23

You raised an interesting question and made a claim that should first be verified.

I checked the behavior of:

$shell -c '[ Makefile -nt SCCS/s.Makefile ] && echo newer'

with various shells. Here are the results:

  • bash Does not work - prints nothing.

  • bosh works

  • dash Does not work - prints nothing.

  • ksh88 Does not work - prints nothing.

  • ksh93 works

  • mksh Does not work - prints nothing.

  • posh prints: posh: [: -nt: unexpected operator/operand

  • yash works

  • zsh works in newer versions, older versions print nothing

So four of nine shells support the -nt feature and implement it correctly. Correctly in this case means: is able to compare time stamps on recent platforms that support sub-second time stamp granularity. Note that the files I selected differ typically only a few microseconds in their time stamps.

Since it is easier to find a working find implementation, I recommend to replace

if [ "$file1" -nt "$file2" ] ; then
    echo newer
fi

by a find based expression.

if [ "$( find "$file1" -newer "$file2" )" ]; then
    echo newer
fi

works at least as long as $file1 does not only contain newlines.

if [ "$( find -L "$file1" -newer "$file2" -exec echo newer \; )" ]; then
    echo newer
fi

is a bit slower but works correctly.

BTW: Regarding make I cannot speak for all make implementations, but SunPro Make supports time comparison with nanosecond granularity since approx. 20 years, while smake and gmake added this feature recently.

  • 1
    Are the "not working" tests not working because of a failure to compare sub-second timestamps (definitely?), or something else? What's the actual timestamps on the files involved in your test? +1 for the testing! – Kusalananda Jun 14 at 8:38
  • 2
    I think the downvote is probably about the confrontational wording. Here, it would be fairer to call it a limitation (significant in some contexts). Some find implementations like busybox or heirloom-toolchest will have the same limitation. – Stéphane Chazelas Jun 14 at 8:45
  • 3
    You'd need -L for the the find version to be equivalent to -nt. It would also fail on file names that start with - or !, (... – Stéphane Chazelas Jun 14 at 8:50
  • 2
    As a matter of fact, I do often get downvotes when I use a confrontational wording. Here it would help if you stated the context (files modified within same timestamp when truncated to second resolution) at the start of the answer. – Stéphane Chazelas Jun 14 at 8:52
  • 1
    @schily, yes, BSDs find have find -f "$file" for that but it's not portable. – Stéphane Chazelas Jun 14 at 8:58

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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