zsh 扩展在非交互式脚本中的工作方式是否有所不同?

zsh 扩展在非交互式脚本中的工作方式是否有所不同?

我目前正在编写一个非常简单的 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。您可以使用echoas 命令而不是轻松查看发生的情况mv

交互式地,看起来您已经null_glob设置了选项。根据zsh文档,默认情况下未设置该选项。取消设置该选项会发生什么取决于是否nomatch设置或取消设置另一个选项 。使用nomatchunset ( 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,第二个扩展为自身,因为它不匹配任何内容。所有这些意味着,如果您使用mvand not echomv应该尝试移动 2 个文件:(dir/file.a很好)和*/*.b(没有这样的文件)。这是大多数 shell中默认发生的情况,例如sh和。ksgbash

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之前插入脚本,则会回到我上面描述的错误行为:它尝试移动不存在的文件。echomv

如果您在/命令setopt null_glob之前插入脚本 ,您将获得在交互式 shell 中获得的行为,即有效。echomv

答案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

相关内容