如何分配一个heredoc值的变量在Bash?

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

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

我将如何分配它到一个变量使用heredoc在Bash?

我需要保留换行符。

我不想转义字符串中的字符,这将是恼人的…

255578 次浏览
VAR=<<END
abc
END

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

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

有用,但里面有个背部痉挛可能会阻止你使用这个。另外,你应该避免使用反勾号,最好使用命令替换符号$(..)

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

使用$()将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提供的答案。

$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT

你可以避免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 选项卡可以在命令行插入制表符。如果您正在使用编辑器,这取决于哪种编辑器,也可以工作,或者您可能必须关闭自动将制表符转换为空格的功能。

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

函数定义:

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

用法:

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


echo "$VAR"

享受

p.s.为不支持read -d的shell创建了“读循环”版本。应该与set -eu未配对引号的一起工作,但没有测试得很好:

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

我发现自己必须读取一个字符串中的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"
$

这里的例子(有^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

将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
)"

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

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

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

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

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

然后你可以像这样打印

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

尼尔的回答分支出来,你通常根本不需要var,你可以像使用变量一样使用函数,而且它比内联或基于__abc0的解决方案更容易阅读。

$ 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"''

感谢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

这里有一种非常优雅的方法,可以避免uoc:

  VAR=$(sed -e 's/[ ]*\| //g' -e '1d;$d' <<'--------------------'
|
| <!DOCTYPE html>
| <html>
|   <head>
|     <script src='script.js'></script>
|   </head>
|   <body>
|     <span id='hello-world'></span>
|   </body>
| </html>
|
--------------------
)

'|'字符定义了空白,打印的字符串中只尊重空白右侧的空白。'1d;$d'剥离了第一行和最后一行,它们只是作为内容周围的上下边距添加的。所有内容都可以缩进到您喜欢的任何级别,除了HEREDOC分隔符,在本例中它只是一堆连字符。

echo "$VAR"


# prints


<!DOCTYPE html>
<html>
<head>
<script src='script.js'></script>
</head>
<body>
<span id='hello-world'></span>
</body>
</html>

真不敢相信我是第一个发这个的人。

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

考虑一下:

#!/bin/bash
mapfile -d '' EXAMPLE << 'EOF'
Hello
こんにちは
今晩は
小夜なら
EOF
echo -n "$EXAMPLE"

收益率:

Hello
こんにちは
今晩は
小夜なら

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

因此,没有必要无用地使用cat,也没有必要招致重新组合数组的代价。

此外,你还能得到这样的好处:

$ echo $EXAMPLE
Hello こんにちは 今晩は 小夜なら

你没有收到@Zombo的方法:

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

奖金

如果你通过head -c -1运行它,你也可以用一种不会不执行的方式去除最后一个换行符:

unset EXAMPLE
mapfile -d '' EXAMPLE < <(head -c -1 << EOF
Hello
こんにちは
今晩は
小夜なら
EOF
)
printf "%q" "$EXAMPLE"
$'Hello\nこんにちは\n今晩は\n小夜なら'