Bash tips: if -e wildcard file check => [: too many arguments
Here is a quick bash tip that might be useful if you need to use inside a bash script a check to see if a wildcard expression of files/folders exists or not. For example:
if [ -e /tmp/*.cache ]
then
echo "Cache files exist: do something with them"
else
echo "No cache files..."
fi
This is using -e (existing file check) that is working fine on individual files. Also the above code might seem to work fine if the result of the expression if one file; but if you have more files returned by the expression this will fail with the following error:
line x: [: too many arguments
This is unfortunately normal as '-e' can take only one parameter, so it will not work with multiple results. This means we have to find a walkaround for this issue... There are probably many solutions for this problem; my idea is to run ls and count the results and then feed that to if. Something like:
files=$(ls /tmp/*.cache)
if [ $files ]
then
echo "Cache files exist: do something with them"
else
echo "No cache files..."
fi
Now this is obviously wrong as if there are no files in the directory we will get a result like this:
ls: cannot access /tmp/*.cache: No such file or directory
and we don’t want to count that. We need to ignore such errors when files are not there, and we will use:
files=$(ls /tmp/*.cache 2> /dev/null)
this way ls errors printed to STDERR now go to /dev/null and we don’t have to worry about them anymore.
This is still not good enough, as if will feed it with more values than it will still fail (when we have more files):
line x: [: /tmp/1.cache: unary operator expected
Obviously the proper way to do this would be to count the number of files and for this we just add "wc -l" to count the result of ls. This should look like this:
files=$(ls /tmp/*.cache 2> /dev/null | wc -l)
if [ "$files" != "0" ]
then
echo "Cache files exist: do something with them"
else
echo "No cache files..."
fi
and if needed we can even use the $files variable that now has the number of file. I hope you will find this useful if you need to do something similar; obviously the file check was just an example, and you would use this based on your needs and inside a script that does something useful
.
>
Tags: bash, tips, Tips & Tricks
6th March 2009, 01:19
To get a correct number of files you may want to use wc -l instead of -w, because:
- filenames might contain spaces
- ls outputs one file per line when not to a terminal (so ls -1 would be redundant here)
Also, != is the string comparison operator, you should use -ne or -gt at least in example code to teach good practices to budding shell scripters
6th March 2009, 02:50
I think you could also do a for loop, which would not trigger an error if no files existed:
for FILE in /tmp/*.cache ; do
# these commands get executed once per file
# with $FILE as the current filename
echo “My file is ${FILE}”
done
6th March 2009, 08:35
@rpetre: thanks! You are right about wc -l, and I just edited the post with the correct information. The final check was intended to be “$files” != “0″, but somehow I put it without quotes. I changed that now to -gt as you suggested as it is cleaner that way. Thanks again for spotting these so fast
@Matt: you are right, and depending on the situation this might be good. If you need to perform an action on the resulted files, when the files don’t exist this will fail when there are no files matching the for expression. Actually in my usage for this, inside the if i had a for just like that, performing an action on all exisitng files, while the else (no files) was performing another action. Thanks for your comment.
9th March 2009, 17:02
You can also enable the bash nullglob option to prevent ls from erroring out when there’s no files:
shopt -s nullglob
To disable: shopt -u nullglob
13th November 2009, 07:51
thx very much
if [ -e *.log ] ;
then
mv *.log ${workDir}
get errors….haha
15th March 2010, 12:37
This tests for the existence of the first file (which suffices: there is AT LEAST 1 file), sets the exit status in $EXISTENCE and then breaks the loop:
for i in *.log; do test -e $i; EXISTENCE=$?; break; done
if [ $EXISTENCE=0 ];
then
echo “Cache files exist: do something with them”
else
echo “No cache files…”
fi
The actual test is only 1 line.
9th April 2010, 13:59
#!/bin/sh
# Scrub Firefox lock file to clean up crashed browsers
#
# list, count and delete target files
#
#
# EH, 08APR10
#
DEST=/home/*/.mozilla/firefox/*/lock
nozfile=$(ls $DEST 2> /dev/null | wc -l) #list files matching wildcards, and count them
if [ "$nozfile" != "0" ] #if there are 1 or more files then…
then
for FILE in $DEST ; do
if ( rm ${FILE} ) #loop through files and if delete succesful print message
then
echo ” ${FILE} deleted by cron script.”
fi
done
fi
exit 0 #end script