I'm currently working on a pretty simple zsh script. What I often do is something like:
mv */*.{a,b} .
When I run that within a zsh script, it seems to expand differently and fail while it works in interactive mode.
% mkdir dir
% touch dir/file.a
% ls file.a
ls: cannot access file.a: No such file or directory
% mv */*.{a,b} .
% ls file.a
file.a
So, this works, but as a script:
% mkdir dir
% touch dir/file.a
% ls file.a
ls: cannot access file.a: No such file or directory
% cat script.sh
#!/usr/bin/zsh
mv */*.{a,b} .
% ./script.sh
./script.sh:2: no matches found: */*.b
So, what's different? What am I doing wrong?
Best Answer
Both are wrong with the
zsh
default option settings. You can easily see what's going on by usingecho
as the command instead ofmv
.Interactively, it looks like you have the
null_glob
option set. According to thezsh
documentation that option is not set by default. What happens with that option unset depends on whether another option,nomatch
, is set or unset. Withnomatch
unset (nonomatch
) you would get this:The expansion happens in 2 steps. First,
*/*.{a,b}
is expanded to 2 words:*/*.a
and*/*.b
. Then each word is expanded as a glob pattern. The first expands todir/file.a
and the second expands to itself because it doesn't match anything. All of this means that, if you usemv
and notecho
,mv
ought to try to move 2 files:dir/file.a
(fine) and*/*.b
(no such file). This is what happens by default in most shells, likesh
andksg
andbash
.The zsh defaults option settings are that
null_glob
is unset andnomatch
is set. Scripts run with the default option settings (unless you change them in~/.zshenv
or/etc/zshenv
, which you relly shouldn't). That means that in scripts, you get this:Since
*/*.b
does not match anything, you get an error due tonomatch
.If you insert
setopt nonomatch
in the script before theecho
/mv
command, you get back to the wrong behaviour as that I describe above: it tries to move a file that does not exist.If you insert
setopt null_glob
in the script before theecho
/mv
command, you get the behaviour you got in your interactive shell, which is that is works.