为什么文件名生成失败会使 zsh 停止处理脚本?

use*_*271 5 zsh

我试图编写一个简短的脚本,该脚本将编写在$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
Run Code Online (Sandbox Code Playgroud)

在 bash 中,它按预期工作,但是一旦内部循环中的文件名生成失败,zsh 就会停止处理脚本:

for pgm in $dir/*; do
           ^^^^^^
  ...
done
Run Code Online (Sandbox Code Playgroud)

结果,由于 my$PATH包含一个不包含任何文件 ( /usr/local/sbin) 的目录,因此在 zsh 中,脚本无法写入之后在目录中找到的可执行文件。

这是另一个显示相同问题的代码:

for f in /not_a_dir/*; do
  echo 'in the loop'
done
echo 'after the loop'
Run Code Online (Sandbox Code Playgroud)

在 bash 中,此命令输出:

in the loop
after the loop
Run Code Online (Sandbox Code Playgroud)

并以代码退出0

在 zsh 中,相同的命令输出:

no matches found: /not_a_dir/*
Run Code Online (Sandbox Code Playgroud)

并以代码退出1

外壳之间的行为差​​异似乎来自nomatch选项,该选项描述于man zshoptions

NOMATCH (+3) <C> <Z>

如果生成文件名的模式没有匹配项,则打印错误,而不是在参数列表中保持不变。这也适用于初始~=.

并在man zshexpn(文件名生成部分)中进行了解释:

该词被替换为与模式匹配的排序文件名列表。如果没有找到匹配的模式,shell 会给出错误消息,除非设置了 NULL_GLOB 选项,在这种情况下,单词被删除;或者除非未设置 NOMATCH 选项,在这种情况下,单词保持不变。

因为如果我 unset nomatch, zsh 的行为就像 bash:

unsetopt nomatch
for f in /not_a_dir/*; do
  echo 'in the loop'
done
echo 'after the loop'
Run Code Online (Sandbox Code Playgroud)

现在我了解了 bash 和 zsh 之间的行为差​​异,以及为什么脚本会在 zsh 中引发错误,但我想了解为什么生成失败的文件名会使 zsh 立即停止处理脚本。因此,我尝试通过用失败的命令(通过执行not_a_cmd)替换失败的文件名生成来重现相同的问题:

for f in ~/*; do
  not_a_cmd
done
echo 'after the loop'
Run Code Online (Sandbox Code Playgroud)

但是此脚本的输出在两个 shell 中几乎相同(除了由于 导致的错误消息not_a_cmd)。特别是,两个 shell 都打印:

after the loop
Run Code Online (Sandbox Code Playgroud)

并且两个 shell 都以代码退出0

为什么失败的文件名生成(如for f in /not_a_dir/*)使 zsh 停止处理脚本,而不是失败的命令(如not_a_cmd)?

我正在使用zsh 5.6.2-dev-0 (x86_64-pc-linux-gnu).