如何在Bash中为变量分配heredoc值?

Nei*_*eil 344 bash heredoc

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

abc'asdf"
$(dont-execute-this)
foo"bar"''
Run Code Online (Sandbox Code Playgroud)

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

我需要保留换行符.

我不想逃避字符串中的字符,这会很烦人......

Pau*_*ce. 479

您可以使用以下方法避免无用的使用cat和更好地处理不匹配的引号:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
Run Code Online (Sandbox Code Playgroud)

如果在回显变量时没有引用变量,则会丢失换行符.引用它保留了它们:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''
Run Code Online (Sandbox Code Playgroud)

如果您想在源代码中使用缩进以提高可读性,请在less-thans之后使用短划线.缩进必须仅使用制表符(无空格)完成.

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

相反,如果要保留结果变量内容中的选项卡,则需要从中删除选项卡IFS.此处doc(EOF)的终端标记不得缩进.

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

Ctrl- 可以在命令行插入选项卡V Tab.如果您使用的是编辑器,则根据哪个编辑器可能也有效,或者您可能必须关闭自动将选项卡转换为空格的功能.

  • 我认为值得一提的是,如果你的脚本中有`set -o errexit`(又名`set -e`)并且你使用它,那么它会终止你的脚本,因为`read`在到达时会返回一个非零的返回码EOF. (104认同)
  • @MarkByers:这是我从不使用`set -e`的原因之一,并且总是建议不要使用它.最好使用正确的错误处理.`trap`是你的朋友.其他朋友:`else`和`||`等等. (13认同)
  • @ulidtko那是因为你在'd`和空字符串之间没有空格; `bash`在`read`看到它的参数之前将'-rd''简化为`-rd`,因此`VAR`被视为`-d`的参数. (6认同)
  • 在这种情况下,避免使用"猫"真的值得吗?使用`cat`将heredoc分配给变量是众所周知的习语.以某种方式使用`read`混淆了一些东西,但收效甚微. (5认同)
  • 在这种格式中,`read`将返回非零退出代码.这使得此方法在启用了错误检查的脚本中不太理想(例如`set -e`). (5认同)
  • @Swiss:[永远不应该使用`set -e`](http://mywiki.wooledge.org/BashFAQ/105) - 进行适当的错误检查 (3认同)
  • @lacostenycoder:`man read`是关于操作系统函数调用的,因此它不直接应用.在这种情况下,`read`是一个内置的Bash,您可以使用`help read`或`man bash`获取有关它的信息.选项`-r`是字面上接受反斜杠而不是使用它们来转义任何字符.`-d`选项将以下参数的第一个字符设置为记录(行)之间的分隔符,而不是默认的换行符.在示例中,我将分隔符设置为空字符串. (3认同)
  • 关于获取非零退出代码,可以使用`|| true` 就在 Heredoc 开始之后,即 `read -r -d '' VAR &lt;&lt;'EOF' || 第一个例子为 true`。 (3认同)
  • 绝对要使用 `-e` / `-o errexit`。bash FAQ 是几十年前写的,已经过时了。您无法预测所有可能的故障场景。不使用“-e”相当于 Visual Basic 中的“ON ERROR RESUME NEXT”。不要这样做。对于这个特定的实例,错误是由于“-d ''”引起的,它告诉“read”在NUL字符(即零字节)处停止。因为输入中没有任何内容,所以该命令正确报告错误,因此屏蔽它(例如使用`|| true)是正确的修复方法。(显然,您也不能将其用于包含 NUL 字节的数据,但无论如何您也不能将它们放入变量中。) (3认同)
  • 是否可以使用它并获得变量替换?我想在我的多行字符串中使用$变量. (2认同)
  • @暂停直至进一步通知。在您保持链接的文档的底部,有一个结论部分。三个结论中有两个建议不要使用 errexit,但一个建议在使用它时要意识到后果。简而言之,这是有争议的。 (2认同)

Nei*_*eil 217

使用$()将输出分配给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
Run Code Online (Sandbox Code Playgroud)

确保用单引号分隔起始END_HEREDOC.

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

感谢您@ephemient的回答.

  • +1.这是最易读的解决方案,至少对我来说是这样.它将变量的名称保留在页面的最左侧,而不是将其嵌入到read命令中. (28认同)
  • PSA:请记住,必须*引用变量*以保留换行符.`echo"$ VAR"`而不是`echo $ VAR`. (12认同)
  • 这对`ash`和OpenWRT来说很不错,其中`read`不支持`-d`. (7认同)
  • @HubertGrzeskowiak它可以防止变量扩展。 (6认同)
  • 由于我无法理解的原因,如果您在heredoc中有一个“未配对的”反引号,则此操作将失败并显示“意外的EOF”错误。 (3认同)
  • 引用“END_HEREDOC”有什么好处? (3认同)
  • @RadonRosborough 如果 Bash v3.x 中存在**不成对的反引号**,则会出现“意外的 EOF”。较新的版本 4.x 和 5.0 没有此问题。不幸的是,这很重要,因为许多 Apple 用户现在和将来都只能使用 Bash 3.2.x。(Apple 刚刚宣布他们的新操作系统版本*不会*获得 Bash 4.x;相反,`zsh` 将成为 10.15+ 中的默认 shell) (2认同)

ttt*_*ttt 77

这是Dennis方法的变体,在脚本中看起来更优雅.

功能定义:

define(){ IFS='\n' read -r -d '' ${1} || true; }
Run Code Online (Sandbox Code Playgroud)

用法:

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

echo "$VAR"
Run Code Online (Sandbox Code Playgroud)

请享用

ps 为不支持的shell 创建了"读取循环"版本read -d.应该使用set -eu不成对的反引号,但没有很好地测试:

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

  • ShellCheck SC2141 说它应该是 `define(){ IFS=$'\n' ...` (添加了 `$`) (4认同)
  • 此解决方案适用于“set -e”集,而所选答案则不然。似乎是因为`http://unix.stackexchange.com/a/265151/20650` (2认同)
  • @fny ps返回状态早已修复 (2认同)

pat*_*pam 33

在此添加评论作为答案,因为我没有足够的代表点来评论您的问题文本.

仍然没有解决方案可以保留换行符.

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

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

  • 实际上这是引用变量如何工作的行为.如果没有引号,它会将它们作为不同的参数插入,空间被删除,而使用引号将整个变量内容视为一个参数 (3认同)

l0s*_*t3d 30

VAR=<<END
abc
END
Run Code Online (Sandbox Code Playgroud)

因为你将stdin重定向到不关心它的东西,即赋值,所以不起作用

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A
Run Code Online (Sandbox Code Playgroud)

有效,但有一个背景可能会阻止你使用它.另外,你应该真的避免使用反引号,最好使用命令替换表示法$(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A
Run Code Online (Sandbox Code Playgroud)

  • @ l0st3d:如此接近...使用`$(cat <<'END'`代替.@ Neil:最后一个换行不会成为变量的一部分,但其余的将被保留. (2认同)
  • @达伦:啊哈!我已经注意到换行符问题,并且在输出变量周围使用引号确实解决了这个问题。谢谢! (2认同)
  • @l0st3d 我只是省略了反引号的提及。 (2认同)
  • 有趣的是,由于第一个示例的怪癖,在紧要关头,您可以将它用于临时注释块,如下所示:`REM=&lt;&lt; 'REM' ... 注释块放在这里 ... REM`。或者更简洁地说,`: &lt;&lt; 'REM' ...`。其中“REM”可以是“NOTES”或“SCRATCHPAD”等。 (2认同)

dim*_*414 10

根据Neil的回答,您通常根本不需要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"''
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案确实很棒。迄今为止最优雅的恕我直言。 (5认同)

小智 9

数组是一个变量,因此在这种情况下mapfile将起作用

mapfile y <<z
abc'asdf"
$(dont-execute-this)
foo"bar"''
z
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样打印

printf %s "${y[@]}"
Run Code Online (Sandbox Code Playgroud)


Fre*_*nan 9

我不敢相信我是第一个发布此内容的人。

\n

@Erman 和 @Zombo 很接近,但mapfile不只是读取数组......

\n

考虑一下:

\n
#!/bin/bash\nmapfile -d \'\' EXAMPLE << \'EOF\'\nHello\n\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf\n\xe4\xbb\x8a\xe6\x99\xa9\xe3\x81\xaf\n\xe5\xb0\x8f\xe5\xa4\x9c\xe3\x81\xaa\xe3\x82\x89\nEOF\necho -n "$EXAMPLE"\n
Run Code Online (Sandbox Code Playgroud)\n

产量:

\n
\n你好\n\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf\n\xe4\xbb\x8a\xe6\x99\xa9\xe3 \x81\xaf\n\xe5\xb0\x8f\xe5\xa4\x9c\xe3\x81\xaa\xe3\x82\x89\n
\n

$\'\'是给 的分隔符mapfile,它永远不会出现,意味着“未分隔”。

\n

因此,不需要无用地使用数组cat,也不需要承受重新组合数组的惩罚。

\n

此外,您还可以获得以下好处:

\n
\nHello\n\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf\n\xe4\xbb\x8a\xe6\x99\xa9\xe3\x81\xaf\n\xe5\xb0\x8f\xe5\xa4\x9c\xe3\x81\xaa\xe3\x82\x89\n
\n

您通过@Zombo\'s 方法没有收到:

\n
$ echo $EXAMPLE\nHello \xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf \xe4\xbb\x8a\xe6\x99\xa9\xe3\x81\xaf \xe5\xb0\x8f\xe5\xa4\x9c\xe3\x81\xaa\xe3\x82\x89\n
Run Code Online (Sandbox Code Playgroud)\n
\nabc\'asdf"\n
\n

奖金

\n

如果你运行它,head -c -1你还可以以一种不会表现不佳的方式摆脱最后一个换行符:

\n
mapfile y <<\'z\'\nabc\'asdf"\n$(dont-execute-this)\nfoo"bar"\'\'\nz\necho $y\n
Run Code Online (Sandbox Code Playgroud)\n
\nabc\'asdf"\n
\n

  • Apple 从 2006 年起仅发布 bash 3.2 :) (2认同)