为什么 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.
解释
在 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.