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

jhr*_*jhr 3 shell scripting zsh wildcards

我目前正在编写一个非常简单的 zsh 脚本。我经常做的是这样的:

mv */*.{a,b} .
Run Code Online (Sandbox Code Playgroud)

当我在 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
Run Code Online (Sandbox Code Playgroud)

所以,这有效,但作为脚本:

% 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
Run Code Online (Sandbox Code Playgroud)

那么,有什么不同呢?我究竟做错了什么?

Cel*_*ada 8

两者的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 .
Run Code Online (Sandbox Code Playgroud)

扩展分两步进行。首先,*/*.{a,b}扩展为2个词:*/*.a*/*.b。然后每个单词被扩展为一个 glob 模式。第一个扩展为dir/file.a,第二个扩展为自身,因为它不匹配任何内容。所有这一切意味着,如果您使用mv和 不echomv应该尝试移动 2 个文件:(dir/file.a很好)和*/*.b(没有这样的文件)。这是大多数 shell 中默认发生的情况,例如shandksgbash

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
Run Code Online (Sandbox Code Playgroud)

由于*/*.b不匹配任何内容,因此您会因nomatch.

如果你setopt nonomatchecho/mv命令之前插入脚本,你会回到我上面描述的错误行为:它试图移动一个不存在的文件。

如果你setopt null_globecho/mv 命令之前插入脚本,你会得到你在交互式 shell 中得到的行为,这是有效的。

  • 另请参阅 `(N)` glob 限定符以在每个 glob 的基础上打开 `nullglob`。`mv */*.{a,b}(N) .` 或 `mv */*.{a,b(N)} .`。在这里,您更愿意使用 `mv */*.(a|b) .` 或 `mv */*.[ab] .`。 (3认同)