为什么zsh使用gnupallel在bash脚本中为我扩展globs?
在bash脚本中,我有一个使用rsync的命令:为什么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试图扩展我
#!/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