如何在Bash中将Heredoc值分配给变量?


374

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

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

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

我需要保留换行符。

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


@JohnM-我刚刚尝试了单引号的Heredoc 分配'EOF',并使用` in the content: if the second line has cd`命令转义了换行符,然后返回:“ .sh:X行:cd:命令未找到 ”; 但是如果我双引号"EOF";然后bash变量${A}不会保留为字符串(它们会被扩展);但随后,换行符保留下来-而且,我没有运行一个命令一个问题cd在第二行(和两个“EOF”和“EOF”似乎也很好地一起玩eval,运行一组存储在命令字符串变量)。干杯!
sdaau 2012年

1
...并添加到我先前的注释中:bash注释双倍"EOF"变量中的“#” (如果通过调用eval $VAR)将导致脚本的所有其余部分都被注释,因为在这里$ VAR将被视为一行; 为了能够#在多行脚本中使用bash 注释,请在eval call: eval“ $ VAR”`中也将双引号引起来。
sdaau,2012年

@sdaau:我在使用eval此方法时遇到了问题,但是由于它是某些程序包的一部分,因此没有跟踪它,该程序包eval的一些变量在其配置文件中定义。错误消息为:/usr/lib/network/network: eval: line 153: syntax error: unexpected end of file。我只是切换到另一个解决方案。
Golar Ramblar

Answers:


515

您可以使用以下方法避免无用的使用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。此处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"''

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


117
我认为值得一提的是,如果脚本中包含set -o errexit(aka set -e)并且使用了该脚本,则它将终止脚本,因为read到达EOF时将返回非零返回码。
Mark Byers

14
@MarkByers:这就是我从不使用set -e并且始终建议不要使用它的原因之一。最好改用适当的错误处理。trap是你的朋友。其他朋友:else||等。
暂停,直到另行通知。

7
cat在这种情况下避免真的值得吗?用cat一个众所周知的习惯将heredoc分配给变量。莫名其妙地使用read模糊处理的东西,恕我直言。
Gregory Pakosz 2014年

6
@ulidtko那是因为您d和空字符串之间没有空格;会在见到其参数之前就bash崩溃-rd''为,因此被视为的参数。-rdreadVAR-d
chepner

6
以这种格式,read将返回非零退出代码。这使得此方法在启用了错误检查的脚本(例如set -e)中不理想。
瑞士

245

使用$()将的输出分配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开头。

请注意,此行末尾定界符END_HEREDOC必须在行上单独存在(因此,圆括号末尾位于下一行)。

感谢您@ephemient的回答。


1
实际上,在某些情况下,此选项允许使用引号。我设法在Perl轻松做到了这一点
Neil

32
+1。至少对于我来说,这是最易读的解决方案。它将变量的名称保留在页面的最左侧,而不是将其嵌入到read命令中。
克莱顿斯坦利

12
PSA:请记住,必须用引号将变量括起来以保留换行符。echo "$VAR"代替echo $VAR
sevko 2014年

7
ashread不支持的OpenWRT 很好-d
David Ehrmann

3
由于我无法理解的原因,如果您在heredoc中有未配对的反引号,则此操作将失败并显示“意外的EOF”错误。
Radon Rosborough

79

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

函数定义:

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

用法:

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

echo "$VAR"

请享用

ps 为不支持的shell 制作了“读取循环”版本read -d。应与工作set -eu未成反引号,但没有测试非常好:

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

1
这似乎仅在表面上起作用。define函数将返回状态1,我不太确定需要更正什么。
2012年

1
这也优于公认的答案,因为除了bash之外,还可以对其进行修改以支持POSIX sh(read函数中的循环,以避免-d ''保留换行符的bashism)。
ELLIOTTCABLE 2014年

与cat-in-a-subshel​​l选项不同,此选项可与Heredoc中未配对的反引号一起使用。谢谢!
Radon Rosborough

此解决方案适用于set -eset,但所选答案不适用。这似乎是由于http://unix.stackexchange.com/a/265151/20650
雏形的

2
@fny PS返回状态一直被固定
TTT

36
VAR=<<END
abc
END

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

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

可以,但是里面有一个小问题,可能会阻止您使用它。另外,您应该避免使用反引号,最好使用命令替换符号$(..)

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

我已经更新了我的问题,以包括$(可执行文件)。另外,您如何保留换行符?
尼尔

2
@ l0st3d:太近了... $(cat <<'END'改用。@Neil:最后一个换行符不会成为变量的一部分,但其余的将被保留。
短暂

1
似乎没有保留任何换行符。运行上面的示例,我看到:“ sdfsdf sdfsdf sdfsfds” ...啊!但是写echo "$A"(即把$ A放在双引号中),您确实会看到换行符!
达伦·库克

1
@Darren:啊哈!我注意到换行符问题,并且在输出变量周围使用引号确实可以解决问题。谢谢!
javadba

1
有趣的是,由于第一个示例的怪癖,您可以在紧急情况下将其用于临时注释块,如下所示:REM=<< 'REM' ... comment block goes here ... REM。或更紧凑地说: << 'REM' ...。“ REM”可能是“ NOTES”或“ SCRATCHPAD”之类的内容
Beejor

34

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

这是不正确的-您可能只是被echo的行为所误导:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines


5
确实,这就是引用变量的方式。如果没有引号,它将以不同的参数插入它们,并以空格分隔,而带有引号的整个变量内容将被视为一个参数
Czipperz

11

分支尼尔的答案,您通常根本不需要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"''

9

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

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

然后您可以像这样打印

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

3

为变量分配一个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机器上进行某种配置?
Kemin Zhou

这可能意味着您在回显变量时,没有在其周围加上引号...像这样尝试:echo "$VAR"
Brad Parks

1

我发现自己必须读取其中包含NULL的字符串,因此这是一个解决方案,它将读取您在其中抛出的任何内容。尽管如果您实际上正在处理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

0

多亏了dimo414的回答,这显示了他出色的解决方案是如何工作的,并表明您也可以轻松地在文本中添加引号和变量:

示例输出

$ ./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”个文件`,所以撇号/单引号可以使事情变得有趣。
dragon788 '18 -5-2

-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.