我目前正在编写一个非常简单的 zsh 脚本。我经常做的事情是这样的:
mv */*.{a,b} .
当我在 zsh 脚本中运行它时,它似乎以不同的方式扩展并且在交互模式下工作时失败。
% 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
所以,这可行,但作为一个脚本:
% 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
那么,有什么不同呢?我究竟做错了什么?
答案1
两者的默认选项设置都是错误的zsh
。您可以使用echo
as 命令而不是轻松查看发生的情况mv
。
交互式地,看起来您已经null_glob
设置了选项。根据zsh
文档,默认情况下未设置该选项。取消设置该选项会发生什么取决于是否nomatch
设置或取消设置另一个选项 。使用nomatch
unset ( nonomatch
) 你会得到这样的结果:
% mkdir dir
% touch dir/file.a
% ls file.a
ls: cannot access file.a: No such file or directory
% echo */*.{a,b} .
dir/file.a */*.b .
扩展分两步进行。首先,*/*.{a,b}
扩展为2个词:*/*.a
和*/*.b
。然后每个单词都被扩展为一个全局模式。第一个扩展为dir/file.a
,第二个扩展为自身,因为它不匹配任何内容。所有这些意味着,如果您使用mv
and not echo
,mv
应该尝试移动 2 个文件:(dir/file.a
很好)和*/*.b
(没有这样的文件)。这是大多数 shell中默认发生的情况,例如sh
和。ksg
bash
zsh 默认选项设置为null_glob
未设置和nomatch
已设置。脚本使用默认选项设置运行(除非您在~/.zshenv
或中更改它们/etc/zshenv
,但实际上不应该这样做)。这意味着在脚本中,您会得到以下内容:
% 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
echo */*.{a,b} .
% ./script.sh
./script.sh:2: no matches found: */*.b
由于*/*.b
不匹配任何内容,因此您会收到错误nomatch
。
如果您在/命令setopt nonomatch
之前插入脚本,则会回到我上面描述的错误行为:它尝试移动不存在的文件。echo
mv
如果您在/命令setopt null_glob
之前插入脚本 ,您将获得在交互式 shell 中获得的行为,即有效。echo
mv
答案2
当您以交互方式运行 zsh 时,它会读取您的~/.zshrc
(以及系统/etc/zshrc
,但如果您的管理员不顽皮,那不是罪魁祸首)。您可能会设置一些选项那里修改了 zsh 扩展命令的方式,特别是extended_glob
(如果 zsh 没有选择向后兼容 20 世纪 90 年代初,当此选项不存在时,这应该是默认值)。执行脚本时不会设置这些选项。
在您的情况下,发生的情况是命令mv */*.{a,b} .
首先被解析为单词列表mv
, */*.a
, */*.b
, .
(除非您关闭大括号扩展)。然后,每个包含通配符的单词都被视为全局模式。默认情况下,在 zsh 中,如果 glob 模式与任何文件都不匹配,则不会执行该命令,并且 zsh 会发出错误信号。显然,在您的 中.zshrc
,您已经打开了该null_glob
选项,这会导致不匹配的模式扩展为空单词列表(即它被删除)。当您执行脚本时,第二个模式*/*.b
与任何文件都不匹配,这会触发您看到的错误。当您以交互方式执行该命令时,该模式将被删除。
null_glob
您可以在脚本中使用 显式打开该选项setopt null_glob
。您还可以使用以下命令将其打开以获取特定模式N
全局限定符:
mv */*.{a,b}(N) .
您不应该在这里使用大括号扩展,而应该使用这或者操作员,它在 zsh 中可用(与普通 sh 不同),因为这正是您的意思 - 不是两种模式,而是与任一模式匹配的文件。
mv */*.(a|b) .
这样就只有一个模式,如果没有文件与该单个模式匹配,您只会收到错误。
如果根本没有文件匹配,此命令将导致错误。您不能只是将其静音,因为这会导致执行(N)
无效命令。mv .
相反,首先测试是否存在匹配,并且mv
仅在存在时执行。
files=(*.(a|b)(N))
if ((#files)); then mv -- $files[@] .; fi