Neither [ -f ... ]
nor [[ -f ... ]]
(nor other file-test operators) are designed to work with patterns (a.k.a. globs, wildcard expressions) - they always interpret their operand as a literal filename.[1]
A simple trick to test if a pattern (glob) matches exactly one file is to use a helper function:
existsExactlyOne() { [[ $# -eq 1 && -f $1 ]]; }
if existsExactlyOne *."$1".*.ext; then # ....
If you're just interested in whether there are any matches - i.e., one or more - the function is even simpler:
exists() { [[ -f $1 ]]; }
If you want to avoid a function, it gets trickier:
Caveat: This solution does not distinguish between regular files directories, for instance (though that could be fixed.)
if [[ $(shopt -s nullglob; set -- *."$1".*.ext; echo $#) -eq 1 ]]; then # ...
- The code inside the command substitution (
$(...)
) does the following:
shopt -s nullglob
instructs bash to expand the pattern to an empty string, if there are no matches
set -- ...
assigns the results of the pattern expansion to the positional parameters ($1
, $2
, ...) of the subshell in which the command substitution runs.
echo $#
simply echoes the count of positional parameters, which then corresponds to the count of matching files;
- That echoed number (the command substitution's stdout output) becomes the left-hand side to the
-eq
operator, which (numerically) compares it to 1
.
Again, if you're just interested in whether there are any matches - i.e., one or more - simply replace -eq
with -ge
.
[1]
As @Etan Reisinger points out in a comment, in the case of the [ ... ]
(single-bracket syntax), the shell expands the pattern before the -f
operator even sees it (normal command-line parsing rules apply).
By contrast, different rules apply to bash's [[ ... ]]
, which is parsed differently, and in this case simply treats the pattern as a literal (i.e., doesn't expand it).
Either way, it won't work (robustly and predictably) with patterns:
- With
[[ ... ]]
it never works: the pattern is always seen as a literal by the file-test operator.
- With
[ ... ]
it only works properly if there happens to be exactly ONE match.
- If there's NO match:
- The file-test operator sees the pattern as a literal, if
nullglob
is OFF (the default), or, if nullglob
is ON, the conditional always returns true, because it is reduced to -f
, which, due to the missing operand, is no longer interpreted as a file test, but as a nonempty string (and a nonempty string evaluates to true)).
- If there are MULTIPLE matches: the
[ ... ]
command breaks as a whole, because the pattern then expands to multiple words, whereas file-test operators only take one argument.