我试图编写一个简短的脚本来编写在以下位置找到的所有可执行程序$PATH
:
for dir in $(tr ':' ' ' <<<"${PATH}"); do
for pgm in $dir/*; do
if command -v "${pgm}" >/dev/null 2>&1; then
echo "${pgm}"
fi
done
done | sort >file
在 bash 中,它按预期工作,但一旦文件名生成在内部循环中失败,zsh 就会停止处理脚本:
for pgm in $dir/*; do
^^^^^^
...
done
结果,由于 my$PATH
包含一个不包含任何文件 ( /usr/local/sbin
) 的目录,因此在 zsh 中,脚本无法写入随后在目录中找到的可执行文件。
这是显示相同问题的另一个代码:
for f in /not_a_dir/*; do
echo 'in the loop'
done
echo 'after the loop'
在 bash 中,此命令输出:
in the loop
after the loop
并退出代码0
。
在 zsh 中,相同的命令输出:
no matches found: /not_a_dir/*
并退出代码1
。
shell 之间的行为差异似乎来自nomatch
选项,该选项在man zshoptions
:
NOMATCH (+3) <C> <Z>
如果用于文件名生成的模式没有匹配项,则打印错误,而不是在参数列表中保持不变。这也适用于初始
~
或=
.
并且还在man zshexpn
(文件名生成部分)中进行了解释:
该词被替换为与模式匹配的排序文件名列表。如果没有找到匹配的模式,shell 会给出一个错误信息,除非设置了 NULL_GLOB 选项,在这种情况下这个词会被删除;或者除非未设置 NOMATCH 选项,在这种情况下,单词保持不变。
因为如果我取消设置nomatch
,zsh 的行为就像 bash:
unsetopt nomatch
for f in /not_a_dir/*; do
echo 'in the loop'
done
echo 'after the loop'
现在我了解了 bash 和 zsh 之间的行为差异,以及为什么脚本会在 zsh 中引发错误,但我想了解为什么文件名生成失败会使 zsh 立即停止处理脚本。因此,我尝试通过用失败的命令(通过执行not_a_cmd
)替换失败的文件名生成来重现相同的问题:
for f in ~/*; do
not_a_cmd
done
echo 'after the loop'
但是这个脚本的输出在两个 shell 中几乎是相同的(除了由于 引起的错误消息not_a_cmd
)。特别是,两个 shell 都打印:
after the loop
并且两个 shell 都以代码退出0
。
为什么文件名生成失败(如for f in /not_a_dir/*
)会使 zsh 停止处理脚本,而不是失败的命令(如not_a_cmd
)?
我正在使用zsh 5.6.2-dev-0 (x86_64-pc-linux-gnu)
.
这是一个功能,而不是错误。?
正如Zsh 手册中的“错误”部分所述:
至于背后的原因,我在 Zsh 邮件列表档案中找到了这个电子邮件交换: