Test if all three filetypes are present in a directory
I want to detect that there is one of each of the following filetypes in a directory .png
, .txt
, and .tar.gz
.
The following works correctly when testing for two of them
shopt -s nullglob
MYDIR=/some_dir
if [ $MYDIR/*.png] && [ $MYDIR/*.txt]; then
echo "All files present"
else
echo "At least one type is missing"
fi
But if I try to test all three using
if [ $MYDIR/*.png] && [ $MYDIR/*.txt] && [ $MYDIR/*.tar.gz]; then
...
or using
if [[ $MYDIR/*.png && $MYDIR/*.txt && $MYDIR/*.tar.gz ]]; then
...
then I don’t get the correct behaviour (it still returns true even if one of the files is missing).
What is going on here?
You absolutely need to use:
shopt -s nullglob
before the test to avoid having false positives, like $MYDIR/*.png
literally.
But you cannot test a wildcard like this in a bash test, you need to test array instead:
txt=( *.txt ) tgz=( *.tar.gz ) png=( *.png )
if [[ ${txt[@]} && ${png[@]} && ${tgz[@]} ]]; then
echo SUCCESS
fi
Another way to test wilcard existence with compgen
hack:
cd "$workdir"
if (compgen -W *.txt && compgen -W *.png && compgen -W *.tar.gz) &>/dev/null; then
echo 'SUCCESS'
else
echo >&2 'At least one filetype missed'
fi
Don’t forget to add bash’s shebang:
#!/bin/bash
or
#!/usr/bin/env bash
[[
is a bash keyword similar to (but more powerful than) the [
command. See http://mywiki.wooledge.org/BashFAQ/031 and http://mywiki.wooledge.org/BashGuide/TestsAndConditionals. Unless you’re writing for POSIX sh, I recommend [[
Example of use:
[[ ${names[@]} ]] && echo 'SUCCESS'
no need to count number of occurrences like you tried.
You can’t test whether a filename globbing pattern matches using [ ... ]
(and you lack space before the final ]
in all [ ... ]
tests, so your code would not work anyway).
The way to do it is to perform the match and count the number of matching names.
shopt -s nullglob # make globs expand to nothing if they don't match
shopt -s dotglob # also match hidden names
error=false
for ext in png txt tar.gz; do
set -- "$MYDIR"/*."$ext"
if [ "$#" -eq 0 ]; then
printf 'Nothing matches "%s"n' "$MYDIR/*.$ext" >&2
error=true
break # possibly, unless you want the user to see all patterns that fail
fi
done
if "$error"; then
echo 'Can not continue' >&2
exit 1
fi
If you feel you want to type more (or just need to preserve the existing list of positional parameters), you could uso a named array. Change the set --
line into names=( "$MYDIR"/*."$ext" )
and the "$#"
into "${#names[@]}"
. Note that all quotes are significant.