man*_*uck 1 bash zsh parameter shell-script quoting
我正在尝试弄清楚如何使用和${parameter%word}进行扩展。它首先尝试制作一个使用 Ghostscript 组合 pdf 的脚本,但我遇到了一些参数扩展的奇怪行为,现在我只是好奇为什么会发生这种行为。$@$*
基本上我试图从每个参数的末尾删除“.pdf”,然后用任意字符串将它们连接起来(我正在使用“-”进行测试),然后在结果的末尾添加“.pdf” 。例如,预期行为是test.sh a.pdf b.pdf c.pdf-> a-b-c.pdf。这是我正在运行的测试脚本:
IFS='-'
echo ${*%.pdf}.pdf
echo "${*%.pdf}.pdf"
a=${*%.pdf}.pdf
b="${*%.pdf}.pdf"
echo $a
echo $b
Run Code Online (Sandbox Code Playgroud)
如果我bash test.sh a.pdf b.pdf c.pdf,我得到:
a b c.pdf
a-b-c.pdf
a b c.pdf
a b c.pdf
Run Code Online (Sandbox Code Playgroud)
如果我zsh test.sh a.pdf b.pdf c.pdf,我得到:
a b c.pdf
a.pdf-b.pdf-c.pdf
a-b-c.pdf
a.pdf-b.pdf-c.pdf
Run Code Online (Sandbox Code Playgroud)
我知道 zsh 和 bash 是不同的,所以我不担心为什么它们给出不同的结果。然而,在每种情况下,构建字符串的 4 种方法中只有一种能够按预期工作(第二种用于 bash,第三种用于 zsh)。
为什么这些看似相似的构造字符串的尝试会产生如此不同的结果?任何见解都值得赞赏。谢谢!
如何${*%word}工作等取决于外壳。POSIX未指定结果。有两种主要的合理行为:转换(前缀或后缀删除)可以应用于每个单词,或者应用于连接单词的结果。在支持数组的 shell 中,很自然地将转换应用于每个单词:这就是 bash 和 ksh93 所做的。在不支持数组的 shell 中,很自然地首先连接单词(这就是 ash/dash 所做的)。例如:
# No arrays: $* joined = \'abc abc\'; strip off b* \xe2\x86\x92 \'a\'\n$ dash -c \'echo ${*%%b*}\' _ abc abc\na\n# Arrays: $* = (\'abc\' \'abc\'); strip off b* from each element \xe2\x86\x92 (\'a\' \'a\'); then join\n$ bash -c \'echo ${*%%b*}\' _ abc abc\na a\nRun Code Online (Sandbox Code Playgroud)\n第一个字符IFS用于连接组成的单词$*。仅当模式可以与该字符匹配时,它才会对删除的内容产生影响。例如:
# No arrays: $* joined = \'abc-def-ghi\'; strip off -* \xe2\x86\x92 \'abc\'\n$ dash -c \'IFS=-; echo "${*%%-*}"\' _ abc-def ghi\nabc\n# Arrays: $* = (\'abc-def\' \'ghi\'); strip off -* from each element \xe2\x86\x92 (\'abc\' \'ghi\'); then join\n$ bash -c \'IFS=-; echo "${*%%-*}"\' _ abc-def ghi\nabc-ghi\nRun Code Online (Sandbox Code Playgroud)\n当替换是在单词上下文中时,扩展到此结束。单词上下文包括双引号和赋值;请参阅何时需要双引号?和shell 变量的扩展以及 glob 和 split 对其的影响以了解更多详细信息。这解释了echo "${*%.pdf}.pdf": 的第一个字符IFS用于连接,并且没有后续的分割,因此 bash 中的输出是a-b-c.pdf。两者的值a也是b如此a-b-c.pdf。
当替换位于列表上下文中(即未加引号)时,如第一个示例所示,结果会进行分词(和通配)。这是基于IFS,因此a-b-c.pdf分为a,b和c.pdf。该echo命令打印三个单词,中间有一个空格。echo $a在您的示例中, and发生完全相同的事情: orecho $b的值在字符处分割。abIFS
Zsh 对待@和*有所不同。作为*参数名称,它在双引号内应用字符串样式的行为(先连接然后转换),否则应用数组样式的行为(转换每个元素)。另一方面,参数@始终被视为数组。因此:
$ zsh -c \'echo "${*%.pdf}"\' _ a.pdf b.pdf c.pdf\na.pdf b.pdf c\n$ zsh -c \'echo ${*%.pdf}\' _ a.pdf b.pdf c.pdf\na b c\n$ zsh -c \'echo "${@%.pdf}"\' _ a.pdf b.pdf c.pdf\na b c\n$ zsh -c \'echo ${@%.pdf}\' _ a.pdf b.pdf c.pdf\na b c\nRun Code Online (Sandbox Code Playgroud)\n与其他 shell 中发生的情况不同,字符串赋值不会导致$*以字符串方式进行处理:双引号才是重要的。这解释了为什么a=${*%.pdf}; echo $a像 一样工作echo ${*%.pdf}而不像a="${*%.pdf}"; echo $a.
使用 时IFS=-,连接时会使用破折号,*无论是由于双引号还是字符串赋值,只要它位于单词上下文中,就会发生这种情况。
# (\'a.pdf\' \'b.pdf\' \'c.pdf); strip each element \xe2\x86\x92 (\'a\' \'b\' \'c\'); print list\n$ zsh -c \'IFS=-; echo ${*%.pdf}\' _ a.pdf b.pdf c.pdf\na b c\n# join \xe2\x86\x92 \'a.pdf-b.pdf-c.pdf\'; strip the single word and print it\n$ zsh -c \'IFS=-; echo "${*%.pdf}"\' _ a.pdf b.pdf c.pdf\na.pdf-b.pdf-c\n# (\'a.pdf\' \'b.pdf\' \'c.pdf); strip each element \xe2\x86\x92 (\'a\' \'b\' \'c\'); `$*` in word context so join \xe2\x86\x92 \'a-b-c\'; print word\n$ zsh -c \'IFS=-; a=${*%.pdf}; echo "$a"\' _ a.pdf b.pdf c.pdf\na-b-c\n# join \xe2\x86\x92 \'a.pdf-b.pdf-c.pdf\'; strip the single word; print the word\n$ zsh -c \'IFS=-; a="${*%.pdf}"; echo "$a"\' _ a.pdf b.pdf c.pdf\na.pdf-b.pdf-c\nRun Code Online (Sandbox Code Playgroud)\n请注意,您几乎不应该使用$*. 仅用 来连接位置参数才有用IFS,并且它使得无法区分IFS通过连接创建的字符与IFS参数中已有的字符。"$@"几乎总是正确的形式。请注意,您确实需要双引号来避免单词扩展(即使在 zsh 中也是如此,尽管在 zsh 中省略引号的影响要小得多)。
为了使您的脚本易于理解,请一次执行一步:去掉每个部分的后缀,然后连接这些部分。使用数组变量来存储中间结果。
\nparts=("${@%.pdf}") # using @ because we want to have array behavior\nIFS=-\njoined="${parts[*]}" # using * and not @ for joining\necho "$joined.pdf"\nRun Code Online (Sandbox Code Playgroud)\n此代码片段在 bash 和 zsh 中的工作方式相同。
\n| 归档时间: |
|
| 查看次数: |
320 次 |
| 最近记录: |