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 process where it's expanded (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
Note that if the glob is passed to an external command, it's only the process that was forked to execute the command that exits:
$ zsh -c '/bin/echo zz*; echo still output and the previous command exited with $?'
zsh:1: no matches found: zz*
still output and the previous command exited with 1
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 -mindepth 1 -maxdepth 1
as an equivalent). ! -name '.*'
is to exclude hidden files like globs do.
Another 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
(with command -x
) and zsh
(with zargs
) 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 -- "$@"
compgen
command: http://stackoverflow.com/questions/2937407/test-whether-a-glob-has-any-matches-in-bash – AnyDev Jul 26 '16 at 12:52