在 bash v3 heredoc 中转义右括号字符

Escaping the close-bracket character in a bash v3 heredoc

我需要在 heredoc 中嵌入 shell 脚本的片段,作为创建云初始化脚本的一部分,以提供 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" 将破坏我们脚本的其余部分。

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

因为这似乎是一个 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}"

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

#!/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}"

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

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

我通过在 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+。