【问题标题】:Escaping the close-bracket character in a bash v3 heredoc在 bash v3 heredoc 中转义右括号字符
【发布时间】:2023-03-13 03:16:01
【问题描述】:

我需要在 heredoc 中嵌入一段 shell 脚本,作为创建 cloud-init 脚本的一部分,以配置 Ubuntu 14.04 LTE 机器。演示该问题的脚本的简化版本如下:

#!/bin/bash

cloudconfig=$(cat <<EOF
    if host \$NAMESERVER 1>/dev/null 2>&1; then
    case \$reason in
    BOUND|RENEW|REBIND|REBOOT) nsupdate -k /var/lib/dhcp/nsupdate.key << EOX
    server \$NAMESERVER
    update delete \$HOST_NAME A
    update add \$HOST_NAME \$TTL A \$HOST_ADDR
    send
    EOX
    ;;
    esac
    fi
EOF
)

echo "${cloudconfig}"

运行上述脚本失败如下:

Little-Net:orchestration minfrin$ bash /tmp/test.sh
could not read key from /var/lib/dhcp/nsupdate.{private,key}: file not found
couldn't get address for '$NAMESERVER': not found

有问题的字符是“REBOOT”右侧的右括号,显而易见的解决方案是转义字符:

BOUND|RENEW|REBIND|REBOOT\) nsupdate -k /var/lib/dhcp/nsupdate.key << EOX

这个反斜杠字符最终出现在最终的 cloudconfig 变量中,这反过来又破坏了输出:

Little-Net:orchestration minfrin$ bash /tmp/test.sh
    if host $NAMESERVER 1>/dev/null 2>&1; then
    case $reason in
    BOUND|RENEW|REBIND|REBOOT\) nsupdate -k /var/lib/dhcp/nsupdate.key << EOX
    server $NAMESERVER
    update delete $HOST_NAME A
    update add $HOST_NAME $TTL A $HOST_ADDR
    send
    EOX
    ;;
    esac
    fi

上面的这个特定片段是正在编写的依赖于变量插值的较大文件的一部分,因此用 >>"EOF" 引用 heredoc 将破坏我们脚本的其余部分。

如何在不通过heredoc 泄漏转义字符的情况下转义“)”字符?

【问题讨论】:

  • 问题显然是不是 “REBOOT”右边的右括号。请注意,如果您认为 ) 是问题所在,请创建一个最基本的示例,该示例仅显示该问题,并且不要在此处发布您的全部内容。让我补充一下,在这种情况下,它会告诉您) 不是问题。
  • @hek2mgl ) 绝对不是问题所在。 /var/lib/dh​​cp/nsupdate.key 显然不存在或看不到,因为错误清楚地表明,不知道为什么 OP 会认为它是括号。@OP 你为什么要在 cat 中这样做
  • 不,) 是问题所在。 shell 将其视为关闭$(...) 并尝试在本地运行nsupdate 命令,这是不正确的。
  • @EtanReisner 你错了,请试试
  • @hek2mgl 我做到了。它失败了。这似乎是一个 bash 版本问题。它适用于here,但对我来说使用 bash 3 失败了。

标签: bash escaping heredoc


【解决方案1】:

因为这似乎是一个 bash 3.x 解析问题(因为它可以在 bash 4.x 中工作,如 here 所示),您需要避免解析器混淆。这似乎对我有用:

#!/bin/bash

rp=")"
cloudconfig=$(cat <<EOF
    if host \$NAMESERVER 1>/dev/null 2>&1; then
    case \$reason in
    BOUND|RENEW|REBIND|REBOOT${rp} nsupdate -k /var/lib/dhcp/nsupdate.key << EOX
    server \$NAMESERVER
    update delete \$HOST_NAME A
    update add \$HOST_NAME \$TTL A \$HOST_ADDR
    send
    EOX
    ;;
    esac
    fi
EOF
)

echo "${cloudconfig}"

【讨论】:

    【解决方案2】:

    由于您在此处的文档中没有实际想要扩展的任何参数,因此我将仅引用整个内容(这样可以为您节省很多显式反斜杠):

    #!/bin/bash
    
    cloudconfig=$(cat <<'EOF'
        if host $NAMESERVER 1>/dev/null 2>&1; then
        case $reason in
        BOUND|RENEW|REBIND|REBOOT) nsupdate -k /var/lib/dhcp/nsupdate.key << EOX
        server $NAMESERVER
        update delete $HOST_NAME A
        update add $HOST_NAME $TTL A $HOST_ADDR
        send
    EOX
        ;;
        esac
        fi
    EOF
    )
    
    echo "${cloudconfig}"
    

    注意EOX也不能缩进,否则使用时这里的文档不会正确终止。

    不过,更简单的是根本不使用此处的文档;只需在参数分配中使用嵌入的换行符。

    #!/bin/bash
    
    cloudconfig='
    if host $NAMESERVER 1>/dev/null 2>&1; then
      case $reason in
        BOUND|RENEW|REBIND|REBOOT) nsupdate -k /var/lib/dhcp/nsupdate.key << EOX
        server $NAMESERVER
        update delete $HOST_NAME A
        update add $HOST_NAME $TTL A $HOST_ADDR
        send
    EOX
        ;;
      esac
    fi'
    
    echo "${cloudconfig}"
    

    如果您需要允许一些参数扩展,您仍然可以使用多行字符串并进行一些修改。每当您需要插值时,关闭单引号并立即打开双引号。展开完成后,关闭double,重新打开single。

    cloudconfig='
        if [[ $SOMEVARIABLE =='"$value"' ]]; then
        ...
    '
    

    【讨论】:

    • OP 表示引用的 heredoc 不是一个选项:“上面的这个特定片段是正在编写的依赖于变量插值的较大文件的一部分,因此用 >>"EOF" 引用 heredoc 是会破坏我们脚本的其余部分。”
    • 可以修改没有here-document的版本以允许一些参数扩展; cloudconfig='$foo='"$foo"', etc.'
    • 使用 cat 和 here-document 创建多行字符串既笨拙又低效。
    • 我并不是说只是指出您所写的解决方案被 OP 直接反驳。而且我认为手动跳入和跳出这样的单引号会更笨拙,更丑陋并且更容易出现某些故障(这并不是说未引用的heredoc不容易出现自己的故障,它绝对是)。
    • 这里给出的测试用例是一个小测试来说明问题,并不是我们完整的解决方案。我们广泛使用了变量插值,只是不在这个测试用例中,因此您的解决方案对我们不适用。
    【解决方案3】:

    我通过在 HEREDOC 中手动转义括号解决了这个问题,然后用 sed 语句删除反斜杠。

    long_string=$(cat << 'HEREDOC'
    This is a string with \( escaped \) parenthesis.
    HEREDOC
    )
    
    long_string=$(echo $long_string | sed -e 's:\\(:(:g' -e 's:\\):):g')
    

    使用 bash 4+ 会更好,但是我必须让团队中的每个人都使用 mac 才能获得 bash 4+。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-15
      • 2019-11-21
      • 2016-01-13
      • 2011-08-09
      • 1970-01-01
      相关资源
      最近更新 更多