find: Updating A Timestamp File
1
1 10.3 Updating A Timestamp File
1 ==============================
1
1 Suppose we have a directory full of files which is maintained with a set
1 of automated tools; perhaps one set of tools updates them and another
1 set of tools uses the result. In this situation, it might be useful for
1 the second set of tools to know if the files have recently been changed.
1 It might be useful, for example, to have a 'timestamp' file which gives
1 the timestamp on the newest file in the collection.
1
1 We can use 'find' to achieve this, but there are several different
1 ways to do it.
1
1 10.3.1 Updating the Timestamp The Wrong Way
1 -------------------------------------------
1
1 The obvious but wrong answer is just to use '-newer':
1
1 find subdir -newer timestamp -exec touch -r {} timestamp \;
1
1 This does the right sort of thing but has a bug. Suppose that two
1 files in the subdirectory have been updated, and that these are called
1 'file1' and 'file2'. The command above will update 'timestamp' with the
1 modification time of 'file1' or that of 'file2', but we don't know which
1 one. Since the timestamps on 'file1' and 'file2' will in general be
1 different, this could well be the wrong value.
1
1 One solution to this problem is to modify 'find' to recheck the
1 modification time of 'timestamp' every time a file is to be compared
1 against it, but that will reduce the performance of 'find'.
1
1 10.3.2 Using the test utility to compare timestamps
1 ---------------------------------------------------
1
1 The 'test' command can be used to compare timestamps:
1
1 find subdir -exec test {} -nt timestamp \; -exec touch -r {} timestamp \;
1
1 This will ensure that any changes made to the modification time of
1 'timestamp' that take place during the execution of 'find' are taken
1 into account. This resolves our earlier problem, but unfortunately this
1 runs much more slowly.
1
1 10.3.3 A combined approach
1 --------------------------
1
1 We can of course still use '-newer' to cut down on the number of calls
1 to 'test':
1
1 find subdir -newer timestamp -and \
1 -exec test {} -nt timestamp \; -and \
1 -exec touch -r {} timestamp \;
1
1 Here, the '-newer' test excludes all the files which are definitely
1 older than the timestamp, but all the files which are newer than the old
1 value of the timestamp are compared against the current updated
1 timestamp.
1
1 This is indeed faster in general, but the speed difference will
1 depend on how many updated files there are.
1
1 10.3.4 Using '-printf' and 'sort' to compare timestamps
1 -------------------------------------------------------
1
1 It is possible to use the '-printf' action to abandon the use of 'test'
1 entirely:
1
1 newest=$(find subdir -newer timestamp -printf "%A%p\n" |
1 sort -n |
1 tail -1 |
1 cut -d: -f2- )
1 touch -r "${newest:-timestamp}" timestamp
1
1 The command above works by generating a list of the timestamps and
1 names of all the files which are newer than the timestamp. The 'sort',
1 'tail' and 'cut' commands simply pull out the name of the file with the
1 largest timestamp value (that is, the latest file). The 'touch' command
1 is then used to update the timestamp,
1
1 The '"${newest:-timestamp}"' expression simply expands to the value
1 of '$newest' if that variable is set, but to 'timestamp' otherwise.
1 This ensures that an argument is always given to the '-r' option of the
1 'touch' command.
1
1 This approach seems quite efficient, but unfortunately it has a
1 problem. Many operating systems now keep file modification time
1 information at a granularity which is finer than one second. Findutils
1 version 4.3.3 and later will print a fractional part with %A@, but older
1 versions will not.
1
1 10.3.5 Solving the problem with 'make'
1 --------------------------------------
1
1 Another tool which often works with timestamps is 'make'. We can use
1 'find' to generate a 'Makefile' file on the fly and then use 'make' to
1 update the timestamps:
1
1 makefile=$(mktemp)
1 find subdir \
1 \( \! -xtype l \) \
1 -newer timestamp \
1 -printf "timestamp:: %p\n\ttouch -r %p timestamp\n\n" > "$makefile"
1 make -f "$makefile"
1 rm -f "$makefile"
1
1 Unfortunately although the solution above is quite elegant, it fails
1 to cope with white space within file names, and adjusting it to do so
1 would require a rather complex shell script.
1
1 10.3.6 Coping with odd filenames too
1 ------------------------------------
1
1 We can fix both of these problems (looping and problems with white
1 space), and do things more efficiently too. The following command works
1 with newlines and doesn't need to sort the list of filenames.
1
1 find subdir -newer timestamp -printf "%A@:%p\0" |
1 perl -0 newest.pl |
1 xargs --no-run-if-empty --null -i \
1 find {} -maxdepth 0 -newer timestamp -exec touch -r {} timestamp \;
1
1 The first 'find' command generates a list of files which are newer
1 than the original timestamp file, and prints a list of them with their
1 timestamps. The 'newest.pl' script simply filters out all the filenames
1 which have timestamps which are older than whatever the newest file is:
1
1 #! /usr/bin/perl -0
1 my @newest = ();
1 my $latest_stamp = undef;
1 while (<>) {
1 my ($stamp, $name) = split(/:/);
1 if (!defined($latest_stamp) || ($tstamp > $latest_stamp)) {
1 $latest_stamp = $stamp;
1 @newest = ();
1 }
1 if ($tstamp >= $latest_stamp) {
1 push @newest, $name;
1 }
1 }
1 print join("\0", @newest);
1
1 This prints a list of zero or more files, all of which are newer than
1 the original timestamp file, and which have the same timestamp as each
1 other, to the nearest second. The second 'find' command takes each
1 resulting file one at a time, and if that is newer than the timestamp
1 file, the timestamp is updated.
1