【问题标题】:How to assign a heredoc value to a variable in Bash?如何将heredoc值分配给Bash中的变量?
【发布时间】:2010-11-13 03:45:12
【问题描述】:

我有这个多行字符串(包括引号):

abc'asdf"
$(dont-execute-this)
foo"bar"''

如何在 Bash 中使用 heredoc 将其分配给变量?

我需要保留换行符。

我不想转义字符串中的字符,那会很烦人...

【问题讨论】:

  • @JohnM - 我刚刚尝试了一个带有单引号'EOF' 的heredoc 分配,带有` in the content: if the second line has cd` 命令的转义换行符,我回来了:“.sh: line X : cd: 找不到命令";但如果我双引号 "EOF";然后 bash 变量 ${A} 不会被保存为字符串(它们会被扩展);但是然后,换行符 被保留 - 而且,我在第二行使用cd 运行命令没有问题(并且'EOF'和“EOF”似乎与eval 一起玩也很好,用于运行存储在字符串变量中的一组命令)。干杯!
  • ... 并添加到我之前的评论中: bash cmets "#" in double-qouted "EOF" 变量,如果通过 eval $VAR 调用,将导致脚本的所有其余部分被评论,因为这里 $VAR 将被视为单行;为了能够在多行脚本中使用 bash # cmets,还要在 eval call: eval "$VAR"` 中添加双引号变量。
  • @sdaau:我在使用这种方法时遇到了eval 的问题,但由于它是evals 在其配置文件中定义的一些变量的某个包的一部分,所以没有追踪它。错误消息是:/usr/lib/network/network: eval: line 153: syntax error: unexpected end of file。我刚刚切换到另一个解决方案。

标签: bash heredoc


【解决方案1】:

您可以避免对cat 的无用使用,并通过以下方式更好地处理不匹配的引号:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

如果您在回显变量时不引用变量,则会丢失换行符。引用它可以保留它们:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

如果您想在源代码中使用缩进以提高可读性,请在小于号后使用破折号。缩进必须只使用制表符(无空格)。

$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
    EOF
$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

如果您想在结果变量的内容中保留制表符,则需要从IFS 中删除制表符。此处文档 (EOF) 的终端标记不得缩进。

$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF
$ echo "$VAR"
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''

可以通过按Ctrl-V Tab在命令行插入制表符。如果您使用的是编辑器,具体取决于哪个编辑器,它可能也可以工作,或者您可能必须关闭自动将制表符转换为空格的功能。

【讨论】:

  • 我认为值得一提的是,如果您的脚本中有set -o errexit(又名set -e)并且您使用它,那么它将终止您的脚本,因为read 返回一个非零返回码当它到达 EOF 时。
  • @MarkByers:这就是我从不使用set -e 的原因之一,并且总是建议不要使用它。最好使用适当的错误处理。 trap 是你的朋友。其他朋友:else|| 等等。
  • 在这种情况下避免cat真的值得吗?使用cat 将heredoc 分配给变量是一个众所周知的习惯用法。不知何故,使用 read 混淆了一些东西,恕我直言。
  • @ulidtko 那是因为d 和空字符串之间没有空格; bashread 看到它的参数之前将-rd'' 折叠为简单的-rd,因此VAR 被视为-d 的参数。
  • 在这种格式中,read 将返回一个非零退出代码。这使得该方法在启用了错误检查的脚本中不太理想(例如set -e)。
【解决方案2】:

使用 $() 将 cat 的输出分配给您的变量,如下所示:

VAR=$(cat <<'END_HEREDOC'
abc'asdf"
$(dont-execute-this)
foo"bar"''
END_HEREDOC
)

# this will echo variable with new lines intact
echo "$VAR"
# this will echo variable without new lines (changed to space character)
echo $VAR

确保以单引号分隔开头的 END_HEREDOC。

请注意,结束 heredoc 分隔符 END_HEREDOC 必须单独在一行中(因此结束括号在下一行)。

感谢@ephemient 的回答。

【讨论】:

  • +1。这是最易读的解决方案,至少对我来说是这样。它将变量的名称留在页面的最左侧,而不是将其嵌入到读取命令中。
  • PSA:记住变量必须被引用以保留换行符。 echo "$VAR" 而不是 echo $VAR
  • 这对ash 和OpenWRT 很好,其中read 不支持-d
  • 由于我无法理解的原因,如果您在 heredoc 中有 unpaired 反引号,则会失败并出现“意外 EOF”错误。
  • 引用'END_HEREDOC'有什么好处?
【解决方案3】:

这是丹尼斯方法的变体,在脚本中看起来更优雅。

函数定义:

define(){ IFS='\n' read -r -d '' ${1} || true; }

用法:

define VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

echo "$VAR"

享受

附言为不支持read -d 的shell 制作了一个'read loop' 版本。应该可以使用 set -eu未配对的反引号,但没有经过很好的测试:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }

【讨论】:

  • 这似乎只在表面上起作用。定义函数将返回状态 1,我不太确定需要更正什么。
  • 这也优于接受的答案,因为它可以修改为支持 POSIX sh 除了 bash (函数中的 read 循环,以避免 -d '' bashism 必须保留换行符)。
  • 此解决方案适用于 set -e 集,而所选答案不适用。好像是因为http://unix.stackexchange.com/a/265151/20650
  • @fny p.s.退货状态早已修复
  • ShellCheck SC2141 说应该是define(){ IFS=$'\n' ...(添加$
【解决方案4】:
VAR=<<END
abc
END

不起作用,因为您将标准输入重定向到不关心它的东西,即分配

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A

有效,但其中有一个可能会阻止您使用它的回击。另外,你真的应该避免使用反引号,最好使用命令替换符号$(..)

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A

【讨论】:

  • 我已更新我的问题以包含 $(executable)。另外,如何保留换行符?
  • @l0st3d: 如此接近...改用$(cat &lt;&lt;'END'。 @Neil:最后一个换行符不会是变量的一部分,但其余部分将被保留。
  • 似乎没有保留任何换行符。运行上面的例子我看到:“sdfsdf sdfsdf sdfsfds”...啊!但是写 echo "$A" (即将 $A 放在双引号中)你会看到换行符!
  • @Darren:啊哈!我注意到换行符问题,并且使用输出变量周围的引号确实解决了这个问题。谢谢!
  • 有趣的是,由于第一个示例的怪癖,在紧要关头,您可以将其用于临时注释块,如下所示:REM=&lt;&lt; 'REM' ... comment block goes here ... REM。或者更简洁,: &lt;&lt; 'REM' ...。其中“REM”可能是“NOTES”或“SCRATCHPAD”等。
【解决方案5】:

仍然没有保留换行符的解决方案。

这不是真的——你可能只是被 echo 的行为误导了:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

【讨论】:

  • 真的这是引用变量的行为。如果没有引号,它将作为不同的参数插入,以空格分隔,而带引号的整个变量内容将被视为一个参数
【解决方案6】:

分支Neil's answer,您通常根本不需要 var,您可以像使用变量一样使用函数,并且它比内联或基于 read 的解决方案更容易阅读。

$ complex_message() {
  cat <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
}

$ echo "This is a $(complex_message)"
This is a abc'asdf"
$(dont-execute-this)
foo"bar"''

【讨论】:

  • 这个解决方案真的很棒。迄今为止最优雅的恕我直言。
  • 这里有很多选择。
【解决方案7】:

数组是一个变量,所以在这种情况下 mapfile 可以工作

mapfile y <<'z'
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

那你就可以这样打印了

printf %s "${y[@]}"

【讨论】:

  • 这里肯定有用例。
【解决方案8】:

为变量分配一个heredoc值

VAR="$(cat <<'VAREOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
VAREOF
)"

用作命令的参数

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"

【讨论】:

  • 当我尝试第一种方法时,行之间似乎没有行终止符。一定是我的 linux 机器上的某种配置?
  • 这可能意味着当你回显你的变量时,你没有在它周围加上引号......试试这样:echo "$VAR"
【解决方案9】:

我发现自己必须读取一个包含 NULL 的字符串,所以这里有一个解决方案,它可以读取你扔给它的 anything。虽然如果你真的在处理 NULL,你需要在十六进制级别处理它。

$ cat > read.dd.sh

read.dd() {
     buf= 
     while read; do
        buf+=$REPLY
     done < <( dd bs=1 2>/dev/null | xxd -p )

     printf -v REPLY '%b' $( sed 's/../ \\\x&/g' <<< $buf )
}

证明:

$ . read.dd.sh
$ read.dd < read.dd.sh
$ echo -n "$REPLY" > read.dd.sh.copy
$ diff read.dd.sh read.dd.sh.copy || echo "File are different"
$ 

HEREDOC 示例(带有 ^J、^M、^I):

$ read.dd <<'HEREDOC'
>       (TAB)
>       (SPACES)
(^J)^M(^M)
> DONE
>
> HEREDOC

$ declare -p REPLY
declare -- REPLY="  (TAB)
      (SPACES)
(^M)
DONE

"

$ declare -p REPLY | xxd
0000000: 6465 636c 6172 6520 2d2d 2052 4550 4c59  declare -- REPLY
0000010: 3d22 0928 5441 4229 0a20 2020 2020 2028  =".(TAB).      (
0000020: 5350 4143 4553 290a 285e 4a29 0d28 5e4d  SPACES).(^J).(^M
0000030: 290a 444f 4e45 0a0a 220a                 ).DONE

【讨论】:

    【解决方案10】:

    感谢dimo414's answer,这显示了他出色的解决方案是如何工作的,并表明您也可以轻松地在文本中包含引号和变量:

    示例输出

    $ ./test.sh
    
    The text from the example function is:
      Welcome dev: Would you "like" to know how many 'files' there are in /tmp?
    
      There are "      38" files in /tmp, according to the "wc" command
    

    test.sh

    #!/bin/bash
    
    function text1()
    {
      COUNT=$(\ls /tmp | wc -l)
    cat <<EOF
    
      $1 Would you "like" to know how many 'files' there are in /tmp?
    
      There are "$COUNT" files in /tmp, according to the "wc" command
    
    EOF
    }
    
    function main()
    {
      OUT=$(text1 "Welcome dev:")
      echo "The text from the example function is: $OUT"
    }
    
    main
    

    【讨论】:

    • 在文本中看到一个不匹配的引用来看看它是如何处理的会很有趣。也许`别害怕,有“$COUNT”个文件`所以撇号/单引号可以让事情变得有趣。
    【解决方案11】:
    $TEST="ok"
    read MYTEXT <<EOT
    this bash trick
    should preserve
    newlines $TEST
    long live perl
    EOT
    echo -e $MYTEXT
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-14
      • 1970-01-01
      • 1970-01-01
      • 2021-06-11
      相关资源
      最近更新 更多