bash变量扩展$ {var:+"..."}在这里 - 文档删除双引号?

And*_*uik 23 bash heredoc quoting expansion

我试图理解为什么Bash在使用${parameter:+word}(使用备用值)进行变量扩展时,在here-document中删除双引号(但不是单引号),例如:

% var=1
% cat <<EOF
> ${var:+"Hi there"}
> ${var:+'Bye'}
> EOF
Hi there
'Bye'
Run Code Online (Sandbox Code Playgroud)

根据手册,后面的"单词" :+用波浪扩展,参数扩展,命令替换和算术扩展处理.这些都不应该做任何事情.

我错过了什么?如何在扩展中获得双引号?

mkl*_*nt0 13

TL;博士

$ var=1; cat <<EOF
"${var:+Hi there}"
${var:+$(printf %s '"Hi there"')}
EOF

"Hi there"
"Hi there"
Run Code Online (Sandbox Code Playgroud)

两个实用的解决方法,包括替代值中的双引号.
嵌入式$(...)方法更麻烦,但更灵活:它允许包含嵌入式双引号,还可以控制是否应该扩展值.


Jens的有用答案Patryk Obara的有用答案都阐明并进一步证明了这个问题.

请注意,有问题的行为同样适用于:

  • (如在其他答案说明)常规的双引号字符串(例如echo "${var:+"Hi there"}",对于第一个解决办法,你必须\-quote周围的"情况;例如,echo "\"${var:+Hi there}\"";奇怪的是,作为Gunstick在关于这个问题的评论指出,用\" "在输出产生的替代值确实在双引号字符串起作用 - 例如,echo "${var:+\"Hi th\"ere\"}"- 与未引用的here-docs不同.)

  • 相关的扩展 ${var+...},${var-...}/ ${var:-...}${var=...}/${var:=...}

  • 此外,还有一个相关的古怪相对于\-处理内部双引号的替代值的双引号字符串/不带引号这里-DOC内: bashksh意外删除的嵌入式\实例; 例如,
    echo "${nosuch:-"a\b"}"出乎意料地收益ab,即使是echo "a\b"单独的收益率a\b- 看到这个问题.

我有没有解释的行为[1] ,但我可以提供实用的解决方案,与所有主要的POSIX兼容的贝壳工作(dash,bash,ksh,zsh):

请注意,替代值中的语法原因"永远不需要实例:替代值被隐式处理为双引号字符串:没有波浪扩展,没有分词,也没有发生全局,但是参数扩展,算术扩展和命令替代执行.

请注意,在涉及替换前缀/后缀删除的参数扩展中,引号确实具有句法含义; 例如:echo "${BASH#*"bin"}"或者echo "${BASH#*'bin'}"- 好奇地,dash不支持单引号.

  • 如果你想环绕整个带引号的替代价值,它有 没有嵌入引号,你想它扩大,
    引用整个扩张,它绕过的问题,"从替代值去除:

    # Double quotes
    $ var=1; cat <<EOF
    "${var:+The closest * is far from   $HOME}"
    EOF
    "The closest * is far from   /Users/jdoe"
    
    Run Code Online (Sandbox Code Playgroud)
    # Single quotes - but note that the alternative value is STILL EXPANDED,
    # because of the overall context of the unquoted here-doc.
    var=1; cat <<EOF
    '${var:+The closest * is far from   $HOME}'
    EOF
    'The closest * is far from   /Users/jdoe'
    
    Run Code Online (Sandbox Code Playgroud)
  • 对于嵌入式引号,或为了防止替代值的扩展,请
    使用嵌入式命令替换(未引用,尽管它的行为就像它被引用一样):

    # Expanded value with embedded quotes.
    var=1; cat <<EOF
    ${var:+$(printf %s "We got 3\" of snow at   $HOME")}
    EOF
    We got 3" of snow at   /Users/jdoe
    
    Run Code Online (Sandbox Code Playgroud)
    # Literal value with embedded quotes.
    var=1; cat <<EOF
    ${var:+$(printf %s 'We got 3" of snow at   $HOME')}
    EOF
    We got 3" of snow at   $HOME
    
    Run Code Online (Sandbox Code Playgroud)

可以根据需要组合这两种方法.


[1] 实际上,替代价值:

  • 表现得像一个隐含的双引号字符串,
  • '实例,如在常规双引号字符串中,被视为文字.

鉴于上述情况,

  • 将嵌入式"实例视为文字也是有意义的,只需将它们传递出去,就像'实例一样.
    相反,遗憾的是,它们被删除了,如果你试图逃避它们\",那么\它也被保留(在这里没有引用 - 文件,但奇怪的是不在双引号字符串中),除了ksh- 值得称道的例外 -其中\实例除去.在zsh,奇怪的是,欲以\"打破了完全扩展(如做不平衡转义所有炮弹的).

    • 更具体地说,双引号在替代值中没有语法功能,但是它们被解析就像它们一样:应用了引用删除,并且你不能"在内部使用(不平衡的)实例而不\"使用它们(它们如同声明,没用,因为\保留了实例).

给定隐式双引号字符串语义,文字$实例必须是\$-escaped,或者必须使用命令替换来嵌入单引号字符串($(printf %s '...')).

  • @PatrykObara:差异可能是意料之外的,但在前缀/后缀删除和字符串替换的上下文中引用_does_提供了一个有用的功能:区分模式元字符(例如,`*`)和文字(例如,``*"`)所以我不会称之为不一致. (2认同)

Jen*_*ens 5

这种行为看起来是故意的——它在我尝试过的所有 Bourne shell 中都是一致的(例如 ksh93 和 zsh 的行为方式相同)。

该行为等同于将 here-doc 视为仅对这些特殊扩展使用双引号。换句话说,你得到相同的结果

$ echo "${var:+"hi there"}"
hi there
$ echo "${var:+'Bye'}"
'Bye'
Run Code Online (Sandbox Code Playgroud)

在 POSIX 规范中只有一个非常微弱的提示,我发现参数扩展中的双引号词会发生一些特殊情况。这是来自参数扩展信息“示例”部分

模式的双引号因放置双引号的位置而异。

"${x#*}"
<星号> 是一个模式字符。
${x#"*"}
文字 <asterisk> 被引用而不是特殊的。

我会读最后一行,建议删除双引号的引号适用于该单词。这个例子对单引号没有意义,并且由于省略,单引号没有引号删除。

更新

我尝试了/bin/sh从 Almquist Shell 派生的 FreeBSD 。这个 shell 输出单引号和双引号。所以行为不再在所有shell 中保持一致,仅在我尝试过的大多数shell 中。

至于在after这个词的扩展中得到双引号:+,我的看法是

$ var=1
$ q='"'
$ cat <<EOF
${var:+${q}hi there$q}
EOF
"hi there"
Run Code Online (Sandbox Code Playgroud)