When you run zsh interactively, it reads your ~/.zshrc
(and also the system /etc/zshrc
but if your administrator isn't naughty that isn't the culprit). You're likely to set a few options there that modify how zsh expands commands, in particular extended_glob
(which should be the default, if zsh didn't choose to be backward compatible with the early 1990s when this option doesn't exist). These options won't be set when you execute a script.
In your case, what's happening is that the command mv */*.{a,b} .
is first parsed into a list of words mv
, */*.a
, */*.b
, .
(unless you turn off brace expansion). Then each word that contains a wildcard character is treated as a glob pattern. By default, in zsh, if a glob pattern doesn't match any file, the command isn't executed and zsh signals an error. Evidently, in your .zshrc
, you've turned on the null_glob
option, which causes the non-matching pattern to expand to an empty list of words instead (i.e. it's removed). When you execute your script, the second pattern */*.b
doesn't match any file, which triggers the error that you see. When you execute the command interactively, that pattern is removed.
You can turn on the null_glob
option explicitly in your script with setopt null_glob
. You can also turn it on for a specific pattern with the N
glob qualifier:
mv */*.{a,b}(N) .
Rather than using brace expansion here, you should use the or operator, which is available in zsh (unlike plain sh), because that's exactly what you mean — not two patterns, but files that match either pattern.
mv */*.(a|b) .
That way there's a single pattern, and you'll only get an error if no file matches that single pattern.
If no file matches at all, this command will result in an error. You can't just silence it with (N)
because that would cause the invalid command mv .
to be executed. Instead, first test if there are matches, and execute mv
only if there are.
files=(*.(a|b)(N))
if ((#files)); then mv -- $files[@] .; fi