为什么 zsh 使用 GNU parallel 在 bash 脚本中为我扩展 glob?

Why does zsh expand globs for me in a bash script using GNU parallel?

在 bash 脚本中,我有一个使用 rsync 的命令:

#!/usr/bin/bash -e
...
parallel rsync --exclude '*to?be?deleted*' ... 
    --files-from some_file /auto $instance_ip:/somewhere_else/

根据 rsync 的文档,他们的 --exclude 字段具有 不同风格的模式匹配

当我在 bash 终端中 运行 时,它工作正常。

但是,运行在 zsh 上使用它给我一个错误,因为 zsh 试图扩展我试图传入的这个文字字符串:

zsh:1: no matches found: *to?be?deleted*

这应该不会发生。为什么 zsh 甚至首先在我的 bash 脚本中扩展我的 glob?我的 zsh 上是否有一些设置可以让两者的行为相同?我不想在 zsh 中开发并部署到具有 bash 的环境并且必须以不同的方式表现。

我正在使用 oh-my-zsh 的插件:

plugins=(
  git
  colored-man-pages
  zsh-autosuggestions
  zsh-syntax-highlighting
)

具体来说,这组命令失败了:

#!/usr/bin/bash -e
find . -name '*filelist' | parallel -j10 rsync --exclude "*to?be?deleted*" testing somewhere_else:/some/where/else

但是使用 rsync 本身的命令,它不会中断。

parallel 正在使用由传递给它的参数组成的字符串启动您的登录实例 shell。您的 bash 脚本在传递参数之前去除引号,因此并行执行相当于

zsh -c "rsync --exclude *to?be?deleted* testing somewhere_else:/some/where/else"

其中的模式 引用。为防止这种情况,将单个字符串作为参数传递给 parallel:

... | parallel -j10 'rsync --exclude "*to?be?deleted*" testing somewhere_else:/some/where/else'

问题出在 GNU Parallel 实用程序上。尽管看起来你正在将一个程序传递给 运行 并带有参数,但它实际上做的是连接参数并将它们传递给 shell.

此外,并行 运行 与您 运行 parallel 相同的 shell,或者根据 [=12= 选择 shell ] 环境变量(这是有问题的,因为这个环境变量也被终端仿真器用来决定哪个交互 shell 到 运行)。无论哪种方式,这就是它选择 zsh 而不是 sh 的原因。你会遇到与 sh 兼容的 shell 的相同问题(bash、dash、ksh、...),但更罕见的是:如果模式不匹配任何内容,sh 将单独保留模式,因此sh 只要当前目录中没有匹配 *to?be?deleted* 的文件,您的脚本就会工作。

手册中给出了解决方法,但有点难找:通过-q选项。手册中有一个很长的章节是关于引用的,你可以忽略 99% 的时间:只要传递 -q 除非你打算 运行 a shell script 而不是 命令 。此外,您应该使用命令的完整路径,否则 parallel 可能会调用一个 shell 内置函数甚至一个函数(如果您的 shell 是 bash)。另外,将 SHELL 设置为 /bin/sh,因为即使使用 -q,Parallel 运行s a shell,并假设它与 sh 兼容(我认为 zsh 足够兼容,但我不完全确定。)。另见 a similar question on Unix Stack Exchange

SHELL=/bin/sh parallel -q -j10 "$(command -v rsync)" --exclude "*to?be?deleted*" testing somewhere_else:/some/where/else

(是的,手册不鼓励你使用-q,但这是错误的。我之前就此与作者争论过。)

GNU 并行版本 < 20140722 使用 $SHELL。更高版本尝试检测哪个 shell GNU Parallel 是从哪个开始的,并使用那个 shell 代替。有关检测的详细信息,请参阅 man parallel_design (Which shell to use). Here it is also explained why GNU Parallel always runs the commands in a shell (Always running commands in a shell)。

如果你不想shell扩展特殊字符,你可以使用-q。 但是,该命令必须是没有重定向和变量赋值的简单命令(参见 man bash)。这个 将引用命令行和参数,以便特殊字符 未被 shell.

解释