2019-05-30 19:05:33 -0500, Steven Penny: [...] > > Of all three designs zsh's is probably the best, but that's > > mostly because it did fix that split+glob upon parameter > > expansion issue of the Bourne shell (when not invoked as sh). > > This is where we differ (I think). I have had limited use of Zsh but the use I > have had of it has been painful. For example with Zsh you cannot use this > "portable" method to check for empty directory: > > % set -- * > zsh: no matches found: * > > % echo $? > 1 > > https://unix.stackexchange.com/questions/492912 > > Granted the method is crude, but its the fact that Zsh fails so harshly, with > "set -e" or similar execution would halt. Compare this with POSIX shell: > > $ set -- * > echo $? > 0
No need for "set -e", in non-interactive shells, a failed glob exits the shell like other syntax errors which is the right thing to do, see below. In interactive shells, it returns to the prompt like for other syntax errors. > Or for example Python: > > >>> import glob > >>> glob.glob('*') > [] > > in both cases, youre essentially getting an empty array, or something that > could > easily be tested to determine if the directory is empty. Sorry for going on a > tangent, but I wanted to explain why I would be wary of any Zsh suggestions as > I disagree with some of their decisions. [...] See https://unix.stackexchange.com/questions/204803/why-is-nullglob-not-default about that. The pre-Bourne sh, csh, zsh, fish, bash -O failglob behaviour is the correct one IMO, and the POSIX sh behaviour is bogus and again could be fixed by adding bash's failglob or zsh's nomatch option to the standard. Here set dir/* or files=(dir/*) is one of the very few commands where you would want a nullglob effect as opposed to a failglob one when the glob doesn't match, but in any case, still not the Bourne/POSIX behaviour (leave the glob unexpanded asis). In zsh, here, you'd write it: set dir/*(N) In ksh93: set dir/~(N)* In bash: saved=$(shopt -p nullglob failglob) || true shopt -s nullglob shopt -u failglob set dir/* eval "$saved" Not much of an improvement over what you need to do in POSIX shells in this case: set -- dir/[*] dir/* case $1$2 in ('dir/[*]dir/*) set --;; (*) shift;; esac (and no, doing [ -e "$1" ] or even [ -e "$1" ] || [ -L "$1" ] to check whether the glob matched is not correct). In fish, which also fails upon non-matching globs, the "set" and "count" commands (and for loops) are actually treated specially. echo *.none # fails if there's no none file set files *.none # doesn't and sets $files to an empty list You don't want nullglob to be the default (like in your python glob()) for all the reasons listed at https://unix.stackexchange.com/questions/204803/why-is-nullglob-not-default You don't want the POSIX behaviour either as passing the glob literally is never what you want, and is even dangerous like in rm -f -- *.[ch] That would remove the file called '*.[ch]' if there was no non-hidden file with a name ending in .c or .h One difference between fish/bash -O failglob and zsh is the scope of the failure upon non-matching globs. In zsh, a failing glob exits the current subshell or shell (unless there's an "always" block to catch the error). (interactively, it returns to the prompt). In fish, it only cancels the command with the failing glob and sets $status to 124. That's similar to what was done in pre-Bourne shells where globs were handled by a separate utility (/etc/glob which gave its name to globs) In bash -O failglob, interactively, like in zsh, it cancels everything and returns to the prompt (or exits a subshell). But in non-interactive shells, it still does the same! That makes it not very usable in scripts. For instance, neither echo *.none; echo done nor { echo *.none echo done } Would output "done" if there was no *.none file, but echo *.none echo done would because upon the failing glob, bash aborts and resumes execution at the next point where it left reading and parsing code like it would in interactive shells. So, in scripts, that means you can only safely use failglob if you put your whole script inside a command group (or a main() function) and then you get the zsh behaviour, or you only use globs inside eval statements (eval 'cmd -- *.txt') and then you get the fish behaviour. In zsh, you can catch the failing glob with a subshell or "always" block: { cmd -- *failing* glob } always { if ((TRY_BLOCK_ERROR)); then echo handle the nomatch error TRY_BLOCK_ERROR=0 # reset the error condition if need be # or let it propagate otherwise fi } or: args=(*failing*(N)) if (($#args)); then cmd -- $args glob else echo handle the no match case fi In bash -O failglob: if eval 'args=(*failing*)'; then cmd -- "${args[@]}" else # beware $args has been left untouched here echo handle the no match error fi You might as well use nullglob instead of failglob and only use globs in array assignments: shopt -s nullglob files=(*) if ((${#files[@]})); then cmd -- "${args[@]}" else echo handle the no match case fi (also beware that in bash, nullglob doesn't cancel failglob, so is ineffective when failglob is on). To me, it makes sense to treat a failing glob the same way as a syntax error. There's no general safe way to handle it. If the script author has not taken steps to handle the case (like by using (N)/nullglob, or "always" block), the safest thing to do is probably to exit. In any case, the Bourne/POSIX sh behaviour is certainly among the worst. -- Stephane