3

I am trying to write a bash script where I am trying to find if some location has empty files or not and send an email if found. I first thought about combining "find" and "mail" together, but if the location has multiple empty files, it would send multiple mails, which I do not want, so I thought of setting a flag variable to zero before find and setting it to 1 inside find if an empty file is found. This is what I tried:

FLAG=0
find $LOCATION -size 0 -type f -exec sh -c 'export FLAG=1' \;
echo $FLAG

but the problem is , even if the location has empty files, the flag value is not changing to 1. What am I doing wrong ?

1
set -- "${LOCATION}/"*
while [ -s "$1" ] ; do shift ; done
[ -e "$1" ] && FLAG=1

The shell builtin [ test ] can be used with the -size operator. From man test:

-s FILE

FILE exists and has a size greater than zero

That won't easily work for recursive searches though.

find will, but for a matter as simple as setting a boolean value, it really is not a good fit for this case. ls can do recursive searches just as fast, can easily be configured to provide file size as the first field for a listing and to list only one entry per line - guaranteed. If you want to set a shell variable's boolean value based on whether or not a recursive file-listing contains a 0-size file then ls is your best bet - find only complicates things. What you're interested in is file properties, not file locations. This is where ls shines, and grepping its output is a breeze.

You can do it this easily:

ls -1aqRsp "$LOCATION" 2>&1 | grep -qv "^ *[^0]\|/"
FLAG=$(($?==0))

That will only set FLAG to 1 if a file - hidden .dot files included - exists in $LOCATION or in one of its child directories with a size of zero. Otherwise, $FLAG is 0.

0

find is forking a new child process when you do -exec. No child process can alter it's parent's environment.

You might consider collecting the file names in question with find and then generating the email you want in a second pass:

find . -type f -size 0 -print >> /var/tmp/find.sz0
...
  • Thanks for the solution, will it help if instead of exec, the output of find is piped into xargs ? – user1600936 Jun 23 '14 at 11:08
  • You would use xargs from a pipe if you wanted to process multiple arguments (here, files) as a bundle to reduce the number of processes spawned. Terminating find with {} + produces the same effect. Neither are needed to generate the list of files you want. – JRFerguson Jun 23 '14 at 11:25
0

That export FLAG=1 is performed in the instance(s) of sh invoked by find. The environment of a process is never copied back to its parent. A shell process that only sets an environment variable and exits accomplishes nothing lasting.

If you want to test whether find matches any file, you can simply test if its output is empty. To avoid potentially generating a long list of files only to discard it once it's been proved to be non-empty, you can truncate the find output at the first opportunity. GNU find has a -quit predicate, and you can also easily tell it to display a single character. In the following snippet, FLAG ends up empty if there is no match:

FLAG=$(find "$LOCATION" -size 0 -type f -printf 1 -quit \;)

The following variant sets FLAG to 0 or 1:

FLAG=$(find "$LOCATION" -size 0 -type f -printf 1 -quit \;)
[ -n "$FLAG" ] || FLAG=0

If you want to stay portable, you can make find print matches and retain only the first line; find will die of a SIGPIPE after head closes its side of the pipe.

if [ -n "$(find "$LOCATION" -size 0 -type f -print | head -n 1 \;)" ]; then
  FLAG=1
else
  FLAG=0
fi

If this was just a simplified example and you need to set more complex variables, there are several possible approaches. I'll just mention a couple of common ones.

Zsh has a lot of use cases of find built in thanks to glob qualifiers. The following snippet sets files to the list of files matched by find $LOCATION -size 0 -type f:

files=(**/*(.DL0))

Ksh and bash also have recursive globs if enabled with set -o globstar and shopt -s globstar respectively. However beware that with bash up to 4.2, ** recurses into symbolic links to directories, which is usually not desirable. You can do further processing in the shell.

FIGNORE=         # include dot files in wildcard matches (the ksh way)
GLOBIGNORE=.:..  # include dot files in wildcard matches (the bash way)
for x in **/*; do
done

If none of the matching file names contain newlines, you can parse the output of find as a newline-delimited list.

find "$LOCATION" -size 0 -type f ! -name '*
*' -print | {
  while read -r empty_file; do
    …
  done
}

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.