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.

Original source