How to check if a glob has an expansion?

With bash:

shopt -s nullglob
files=(/mydir/*.gz)
((${#files[@]} == 0)) || gzip -d -- "${files[@]}"

With zsh:

files=(/mydir/*.gz(N))
(($#files == 0)) || gzip -d -- $files

Note that in zsh, without (N), like in pre-Bourne shells, csh or tcsh, if the glob doesn't match, the command is not run, you'd only do the above to avoid the resulting error message (of no match found as opposed to gzip failing on the expanded glob in the case of bash or other Bourne-like shells). You can achieve the same result with bash with shopt -s failglob.

In zsh, a failing glob is a fatal error that causes the shell (when not interactive) to exit. You can prevent your script from exiting in that case either by using a subshell or using zsh error catching mechanism ({ try-block; } always { error-catching; }), (or by setting the nonomatch (to work like sh), nullglob or noglob option of course, though I wouldn't recommend that):

$ zsh -c 'echo zz*; echo not output'
zsh:1: no matches found: zz*
$ zsh -c '(echo zz*); echo output'
zsh:1: no matches found: zz*
output
$ zsh -c '{echo zz*;} always {TRY_BLOCK_ERROR=0;}; echo output'
zsh:1: no matches found: zz*
output
$ zsh -o nonomatch -c 'echo zz*; echo output'
zz*
output

With ksh93

ksh93 eventually added a mechanism similar to zsh's (N) glob qualifier to avoid having to set a nullglob option globally:

files=(~(N)/mydir/*.gz)
((${#files[@]} == 0)) || gzip -d -- "${files[@]}"

POSIXly

Portably in POSIX sh, where non-matching globs are passed unexpanded with no way to disable that behaviour (the only POSIX glob related option is noglob to disable globbing altogether), the trick is to do something like:

set -- /mydir/[*].gz /mydir/*.gz
case $#$1$2 in
   '2/mydir/[*].gz/mydir/*.gz') : no match;;
   *) shift; gzip -d -- "$@"
esac

The idea being that if /mydir/*.gz doesn't match, then it will expand to itself (/mydir/*.gz). However, it could also expand to that if there was one file actually called /mydir/*.gz, so to differentiate between the cases, we also use the /mydir/[*].gz glob that would also expand to /mydir/*.gz if there was a file called like that.

As that's pretty awkward, you may prefer using find in those cases:

find /mydir/. ! -name . -prune ! -name '.*' \
   -name '*.gz' -type f -exec gzip -d {} +

The ! -name . -prune is to not look into subdirectories (some find implementations have -depth 1 or -mindepth 1 -maxdepth 1 as an equivalent). ! -name '.*' is to exclude hidden files like globs do.

A benefit is that it still works if the list of files is too big to fit in the limit of the size of arguments to an executed command (find will run several gzip commands if need to avoid that, ksh93 and zsh also have mechanisms to work around that).

Another benefit is that you will get error messages if find cannot read the content of /mydir or can't determine the type of the files (globs would just silently ignore the problem and act as if the corresponding files don't exist).

A small down side is that you lose the exact value of gzip's exit status (if any one gzip invocation fails with a non-zero exit status, find will still exit with a non-zero exit status (though not necessarily the same) though, so that's good enough for most use cases).

Another benefit is that you can add the -type f to avoid trying to uncompress directories or fifos/devices/sockets... whose name ends in .gz. Except in zsh (*.gz(.) for regular files only), globs cannot filter by file types, you'd need to do things like:

set --
for f in /mydir/*.gz
  [ -f "$f" ] && [ ! -L "$f" ] && set -- "$@" "$f"
done
[ "$#" -eq 0 ] || gzip -d -- "$@"

One way to do this is:

shopt -s nullglob
for f in /mydir/*.gz; do
  gzip -d /mydir/*.gz
  break
done

The for loop with nullglob set on will only execute the loop at all if the glob has an expansion, and the unconditional break statement ensures that the gzip command will only be executed once.

It's a bit funny because it uses a for loop as an if, but it works.