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

时间:2018-05-04 20:15:39

标签: bash zsh 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 脚本中扩展我的全局?我的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的命令本身,它不会中断。

3 个答案:

答案 0 :(得分:4)

问题是GNU并行实用程序。即使看起来你正在传递一个用参数运行的程序,它实际上做的是连接参数并将它们传递给shell。

此外,Parallel或者运行与您运行的parallel相同的shell,或者根据SHELL环境变量选择shell(这是有问题的,因为终端模拟器也使用此环境变量来决定哪个交互式shell运行)。无论哪种方式,这就是为什么它选择zsh而不是sh。你有一个与sh(bash,dash,ksh,...)兼容的shell有同样的问题,但更少见:如果他们不匹配任何东西,sh就会留下模式,所以只要当前目录中没有匹配*to?be?deleted*的文件,您的脚本就会起作用。

解决方案在手册中给出,但有点难以找到:传递-q选项。本手册有很长的引用章节,您可以忽略99%的时间:只需传递-q,除非您打算运行 shell脚本而不是命令。此外,您应该使用命令的完整路径,否则parallel可能会调用shell内置函数甚至函数(如果您的shell是bash)。另外,将SHELL设置为/bin/sh,因为即使使用-q,Parallel也会运行一个shell,并假设它与sh兼容(我认为zsh足够兼容,但我和#39;我不完全确定。)另请参阅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,但这是错误的。我之前曾与作者就这一点进行过争论。)

答案 1 :(得分:2)

parallel正在使用由传递参数组成的字符串启动登录shell的实例。您的bash脚本在传递参数之前删除引号,因此parallel执行等效的

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'

答案 2 :(得分:1)

GNU并行版本< 20140722使用$SHELL。更高版本尝试检测从哪个shell GNU Parallel启动,并使用该shell代替。有关检测的详细信息,请参阅man parallel_designWhich shell to use)。这里还解释了为什么GNU Parallel总是在shell中运行命令(Always running commands in a shell)。

如果您不希望shell扩展特殊字符,则可以使用-q。 但是,该命令必须是一个简单的命令(参见man bash),没有重定向和没有变量赋值。这个 将引用命令行和参数,以便特殊字符 不是由shell解释的。​​