如何将 HEREDOC 文本带入 shell 脚本变量?

Kev*_*vin 12 shell-script posix

我正在尝试以符合 POSIX 的方式将 HEREDOC 文本带入 shell 脚本变量中。我试过这样:

#!/bin/sh

NEWLINE="
"

read_heredoc2() {
  while IFS="$NEWLINE" read -r read_heredoc_line; do
    echo "${read_heredoc_line}"
  done
}

read_heredoc2_result="$(read_heredoc2 <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|



HEREDOC
)"

echo "${read_heredoc2_result}"
Run Code Online (Sandbox Code Playgroud)

这产生了以下错误:

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _  | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|
Run Code Online (Sandbox Code Playgroud)

以下工作,但我不喜欢使用随机输出变量有多么笨重:

#!/bin/sh

NEWLINE="
"

read_heredoc1() {
  read_heredoc_first=1
  read_heredoc_result=""
  while IFS="$NEWLINE" read -r read_heredoc_line; do
    if [ ${read_heredoc_first} -eq 1 ]; then
      read_heredoc_result="${read_heredoc_line}"
      read_heredoc_first=0
    else
      read_heredoc_result="${read_heredoc_result}${NEWLINE}${read_heredoc_line}"
    fi
  done
}

read_heredoc1 <<'HEREDOC'

                        _                            _ _            
                       | |                          | (_)           
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___ 
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |                                                
            |___/|_|                                                



HEREDOC

echo "${read_heredoc_result}"
Run Code Online (Sandbox Code Playgroud)

正确的输出:

                        _                            _ _            
                       | |                          | (_)           
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___ 
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |                                                
            |___/|_|                                                
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

Mic*_*mer 15

问题是,在 Bash 中,内部$( ... )转义(和其他)序列被解析,即使heredoc 本身没有它们。你会得到一条双线,因为\逃脱了换行符。您所看到的实际上是 Bash 中的解析问题 - 其他 shell 不会这样做。反引号在旧版本中也可能是一个问题。我已经确认这是 Bash 中的一个错误,它将在未来的版本中修复。

您至少可以大大简化您的功能:

func() {
    res=$(cat)
}
func <<'HEREDOC'
...
HEREDOC
Run Code Online (Sandbox Code Playgroud)

如果要选择输出变量,可以对其进行参数化:

func() {
    eval "$1"'=$(cat)'
}
func res<<'HEREDOC'
...
HEREDOC
Run Code Online (Sandbox Code Playgroud)

或者一个相当丑陋的没有eval

{ res=$(cat) ; } <<'HEREDOC'
...
HEREDOC
Run Code Online (Sandbox Code Playgroud)

{}需要,而不是(),这样变量仍然可用之后。

根据您执行此操作的频率和目的,您可能更喜欢这些选项中的一个或另一个。最后一个是一次性最简洁的。


如果您能够使用zsh,您的原始命令替换 + heredoc 将按原样工作,但您也可以进一步折叠所有这些:

x=$(<<'EOT'
...
EOT
)
Run Code Online (Sandbox Code Playgroud)

Bash 不支持这一点,我认为任何其他 shell 也不会遇到您遇到的问题。


ImH*_*ere 6

关于OP解决方案:

  • 如果允许使用某个常量变量,则不需要 eval 来分配变量。

  • 也可以实现调用接收 HEREDOC 的函数的一般结构。

在解决了这两个项目的所有(合理)shell 中都有效的解决方案是:

#!/bin/bash
nl="
"

read_heredoc(){
    var=""
    while IFS="$nl" read -r line; do
        var="$var$line$nl"
    done 
}


read_heredoc <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|

HEREDOC

read_heredoc2_result="$str"

printf '%s' "${read_heredoc2_result}"
Run Code Online (Sandbox Code Playgroud)

原始问题的解决方案。

自 bash 2.04(以及最近的 zsh、lksh、mksh)以来有效的解决方案。
查看下面的更便携 (POSIX) 版本。

#!/bin/bash
read_heredoc() {
    IFS='' read -d '' -r var <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|



HEREDOC

}

read_heredoc
echo "$var"
Run Code Online (Sandbox Code Playgroud)

核心命令

IFS='' read -d '' -r var <<'HEREDOC'
Run Code Online (Sandbox Code Playgroud)

工作原理如下:

  1. 该词HEREDOC被(单)引用,以避免对后面的文本进行任何扩展。
  2. “here doc”内容在标准输入中提供<<
  3. 该选项-d ''强制read吞下“here doc”的全部内容。
  4. -r选项避免解释反斜杠引号字符。
  5. 核心命令类似于read var.
  6. 最后一个细节是IFS='',这将避免读取删除默认 IFS: 中的前导或尾随字符spacetabnewline

在 ksh 中,该-d ''选项的空值不起作用。
作为一种解决方法,如果文本没有“回车”,则 a-d $'\r'有效(当然,如果将 a$'\r'添加到每行的末尾)。


添加的(在评论中)要求是生成符合 POSIX 的解决方案。

POSIX

扩展这个想法,使其仅与 POSIX 选项一起运行。
这意味着主要没有-dfor read。这会强制读取每一行。
这反过来又迫使需要一次捕获一条线。
然后,要构建var一个尾随的新行,必须添加(因为读取删除了它)。

#!/bin/sh

nl='
'

read_heredoc() {
    unset var
    while IFS="$nl" read -r line; do
        var="$var$line$nl"
    done <<\HEREDOC

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \ 
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/ 
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___| 
             __/ | | 
            |___/|_| 



HEREDOC

}

read_heredoc
printf '%s' "$var"
Run Code Online (Sandbox Code Playgroud)

这在所有合理的外壳中都有效(并且已经过测试)。