Bash check if file exists with double bracket test and wildcards
bash
Solution
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.
Problem
I am writing a Bash script and need to check to see if a file exists that looks like `*.$1.*.ext` I can do this really easily with POSIX test as `[ -f *.$1.*.ext ]` returns true, but using the double bracket `[[ -f *.$1.*.ext ]]` fails. This is just to satisfy curiosity as I can't believe the extended testing just can't pick out whether the file exists. I know that I can use `[[ `ls *.$1.*.ext` ]]` but that will match if there's more than one match. I could probably pipe it to `wc` or something but that seems clunky. Is there a simple way to use double brackets to check for the existence of a file using wildcards? EDIT: I see that `[[ -f `ls -U *.$1.*.ext` ]]` works, but I'd still prefer to not have to call ls.