Test if there are files matching a pattern in order to execute a script
[ -f /*.txt ]
would return true only if there's one (and only one) non-hidden file in /
whose name ends in .txt
and if that file is a regular file or a symlink to a regular file.
That's because wildcards are expanded by the shell prior to being passed to the command (here [
).
So if there's a /a.txt
and /b.txt
, [
will be passed 5 arguments: [
, -f
, /a.txt
, /b.txt
and ]
. [
would then complain that -f
is given too many arguments.
If you want to check that the *.txt
pattern expands to at least one non-hidden file (regular or not), in the bash
shell:
shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"
shopt -s nullglob
is bash
specific (shopt
is, nullglob
actually comes from zsh
), but shells like ksh93
, zsh
, yash
, tcsh
have equivalent statements.
With zsh
, the test for are there files matching a pattern can be written using an anonymous function and the N
(for nullglob
) and Y1
(to stop after the first find) glob qualifier:
if ()(($#)) *.txt(NY1); then
do-something
fi
Note that those find those files by reading the contents of the directory, it doesn't try and access those files at all which makes it more efficient than solutions that call commands like ls
or stat
on that list of files computed by the shell.
The standard sh
equivalent would be:
set -- [*].txt *.txt
case "$1$2" in
('[*].txt*.txt') ;;
(*) shift; script "$@"
esac
The problem is that with Bourne or POSIX shells, if a pattern doesn't match, it expands to itself. So if *.txt
expands to *.txt
, you don't know whether it's because there's no .txt
file in the directory or because there's one file called *.txt
. Using [*].txt *.txt
allows to discriminate between the two.
Now, if you want to check that the *.txt
matches at least one regular file or symlink to regular file (like your [ -f *.txt ]
suggests you want to do), or that all the files that match *.txt
are regular files (after symlink resolution), that's yet another matter.
With zsh
:
if ()(($#)) *.txt(NY1-.); then
echo "there is at least one regular .txt file"
fi
if ()(($#)) *.txt(NY1^-.); then
echo "there is at least one non-regular .txt files"
fi
(remove the -
if you want to do the test prior to symlink resolution, that is consider symlinks as non-regular files whether they point to regular files or not).
You could always use find
:
find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script
Explanation:
find .
: search the current directory-maxdepth 1
: do not search subdirectories-type f
: search only regular filesname "*.txt"
: search for files ending in.txt
2>/dev/null
: redirect error messages to/dev/null
| grep -q .
: grep for any character, will return false if no characters found.&& ./script
: Execute./script
only if the previous command was successful (&&
)
A possible solution is also Bash builtin compgen
. That command returns all possible matches for a globbing pattern and has an exit code indicating whether any files matched.
compgen -G "/*.text" > /dev/null && ./script
I found this question while looking for solutions that are faster though.