1

I am writing a script which needs to check if any of the files in a given directory have been modified recently. I thought I could simply use find, however it reports success even if it doesn't find a match:

$ touch afile
$ find . -mtime -1 -iname ????*
./afile
$ echo $?
0
$ find . -mtime +1 -iname ????*
$ echo $?
0

(I'm just using the iname predicate to exclude '.' here - but same behaviour with -type f)

I would prefer not to have to parse the output of ls as this can be temperamental.

Using test -n "$(find . -mtime -1 -iname ????*)" seems to give the desired result but doesn't strike me as a particularly elegant solution.

I don't need to know which files have been modified - only if ANY have changed recently (where "recently" will usually be 1 day).

Is there a better way to do this?

(searching Google, I just get lots of SEO crap on how to execute a simple find)

symcbean
  • 5,540

4 Answers4

2

Yes, as you have seen, you cannot use find's exit code for something like this. The exit code is only about whether or not the command ran successfully, it has nothing to do with whether or not files were found. After all, finding no files isn't a failure, it is a success: you got the correct result, no files. This is explained in man find (the quote is from GNU find):

EXIT STATUS

find exits with status 0 if all files are processed successfully, greater than 0 if errors occur. This is deliberately a very broad description, but if the return value is non-zero, you should not rely on the correctness of the results of find.

When some error occurs, find may stop immediately, without completing all the actions specified. For example, some starting points may not have been examined or some pending program invocations for -exec ... {} + or -execdir ... {} + may not have been performed.

You could indeed use -n $(find ...), I don't really see an issue there. Since file names cannot contain NUL, I cannot think of a case that would break that. If you want something slightly cleaner, you could try using an array and then testing whether the number of elements in the array is more than 0. Something like this:

$ findResults=( $(find . -mtime -1 -iname '????*' | head -n1) )
$ [[ ${#findResults[@]} -gt 0 ]] && echo "Found files!" || echo "No files found!"
Found files!

$ findResults=( $(find . -mtime +1 -iname '????*' | head -n1) ) $ [[ ${#findResults[@]} -gt 0 ]] && echo "Found files!" || echo "No files found!" No files found!

Frankly, however, I am not sure this is any better than the simpler [[ -n $(find ...) ]] && echo found || echo nope.

terdon
  • 242,166
0

mtree is specifically designed for this sort of thing, such as verifying that a directory full of binaries has not been overwritten with malicious or corrupted code, or any use case where you want to check whether a set of files are free from unexpected changes (whether in data or metadata).

Create (-c) a reference file my_dir.mtree of your directory (-p my_dir), including the -K sha256 digest:

$ sudo mtree -c -p my_dir -K sha256 > my_dir.mtree

The next time you want to check whether any files have been modified, run mtree again giving it just the -p path option and your mtree specification file:

$ sudo mtree -p my_dir < my_dir.mtree

Any files that differ will be listed.

Jim L.
  • 7,997
  • 1
  • 13
  • 27
0

With zsh,

if ()(($#)) **/????*(NDY1m-1); then
  echo There are files or directories last modified within the last 24 hours
fi
  • () {body} args defines and runs an anonymous function whose body here is the (($#)) arithmetic expression which returns true if $# (the number of arguments) is non-zero
  • **/ any level (including 0) of directories, short for extendedglob's (*/)#. Note that . itself is not considered, you can replace **/ with {.,**/} if you want it to be considered.
  • N: nullglob glob qualifier: don't complain if there's no match and instead pass an empty list to the anonymous function.
  • D: dotglob, don't skip hidden files.
  • Y1: stop at the first match as an optimisation
  • m-1: last modified in the last day (24 hours) like find's -mtime -1.

With set -o extendedglob, you can use ?(#c4,) instead of ????* to match on files with at least 4 characters in their name.

0
if find . -name '????*' -mtime -1 | LC_ALL=C grep -q '^'; then
  echo There are files last modified within the last 24 hours
fi

May be slightly more efficient if there's a large number of matching files, as grep -q will exit as soon as it reads one line of input, after which find will be killed the next time it writes something to the pipe.

As find will buffer its output as it's going to a pipe, it won't be killed as soon as the first file is found though.

With GNU or FreeBSD find, you can do:

if [ -n "$(find . -name '????*' -mtime -1 -print -quit)" ]...

On NetBSD, the equivalent is with -exit. With GNU find, you can replace -print with -printf . as a tiny optimisation.

You could also do things like:

if [ -n "$(
  find . -name '????*' -mtime -1 -exec sh -c 'echo .; kill -s PIPE "$PPID"' ';'
  )" ]...

Where find invokes sh to output something and kill its parent.