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

为什么zsh使用gnupallel在bash脚本中为我扩展globs?,bash,zsh,gnu-parallel,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试图扩展我

在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脚本中扩展globs呢?在我的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
脚本在传递参数之前去掉引号,因此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'

问题在于GNU并行实用程序。尽管看起来像是在向它传递一个带参数运行的程序,但实际上它所做的是连接参数并将其传递给shell

此外,Parallel要么运行与您运行的
Parallel
相同的shell,要么根据
shell
环境变量选择shell(这是有问题的,因为终端模拟器也使用此环境变量来决定运行哪个交互式shell)。无论哪种方式,这就是为什么它选择zsh而不是sh。对于与sh兼容的shell(bash、dash、ksh等),您也会遇到同样的问题,但更为罕见的是:如果模式与任何内容都不匹配,sh会单独保留模式,因此,对于sh,只要当前目录中没有匹配要删除的文件*,脚本就可以工作

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


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

GNU并行版本<20140722使用
$SHELL
。更高版本尝试检测哪个shell GNU并行是从哪个shell启动的,并使用该shell。有关检测的详细信息,请参见
manparallel_design
()。这里还解释了为什么gnupallel总是在shell()中运行命令

如果不希望shell扩展特殊字符,可以使用
-q
。 但是,该命令必须是没有重定向和变量分配的简单命令(请参见
manbash
)。这 将引用命令行和参数,以便使用特殊字符
shell没有解释。

你是如何运行脚本的?
$bash my_script.sh
那就特别令人惊讶了。你的脚本中的
..
是什么,没有它你会得到同样的东西吗?嗯,
..
很长,但是它正在设置一个云实例并进行一些预处理。我正在尝试对文件进行
rsync
。我试着做了一个简单的
rsync
,它成功了。在我的问题中,我已经发送了一个MVCE:)您在bash脚本中引用了该模式,但是
parallel
的所有额外参数都被传递到另一个shell,减去
bash
已经去掉的引号。我不确定责怪bash将参数串联在一起形成字符串是否公平,然后将该字符串传递给shell。如果有人这样做,即使做得太频繁(比如
ssh
),那也是一种糟糕的做法;我看不出它还能做什么。我只是指出这就是
zsh
试图扩展被认为是引用的模式的原因。(或者我错过了删除的注释吗?)我明白了。这对并行公司来说是相当危险的,我已经把它改成了一个完整的引用。谢谢:)把我打扮成一个纯粹主义者——我的观点是,
ssh
(或
parallel
或任何其他程序)隐式运行
“${SHELL:-sh}”-c
,这显然是错误的,并且给定一个argv用于调用另一个软件段的程序应该将它传递给一个execv风格的调用,而不做任何修改,而不是试图建立一个字符串。因此,在那些parallel向用户保证需要调用shell而不将
sh
作为参数传递的地方,我的立场是这是一个糟糕得令人无法忍受的设计。这是一个非常全面的答案。谢谢:)我打赌这已经发生在无数使用不同于bash的shell的人身上,他们想知道为什么他们的bash脚本会调用zsh。根据它的描述,甚至不清楚
-q
是做什么的。它是否在每个单词周围添加了一层引号,比如
'rsync'--排除''.''要删除'.''测试'.'
?@chepner很好,它传递了一个shell并引用了字符串@OneRaynyDay@chepner
-q
引用命令和参数。引用的实际方式取决于外壳,但对于大多数外壳,它是被引用的<代码
SHELL=/bin/sh parallel -q -j10 "$(command -v rsync)" --exclude "*to?be?deleted*" testing somewhere_else:/some/where/else