我有这个多行字符串(包括引号):
abc'asdf"
$(dont-execute-this)
foo"bar"''
如何在 Bash 中使用 heredoc 将其分配给变量?
我需要保留换行符。
我不想转义字符串中的字符,那会很烦人...
您可以避免无用地使用
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 在命令行中插入制表符。如果您使用的是编辑器,这可能也有效,或者您可能必须关闭自动将制表符转换为空格的功能。
使用 $() 将
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 的内容,所以
dont-execute-this
不会被执行。
注意结束heredoc定界符
END_HEREDOC
必须单独在一行中(因此结束括号)
在下一行)。
感谢
@ephemient
的回答。
这是丹尼斯方法的变体,在脚本中看起来更优雅。
函数定义:
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"; }
VAR=<<END
abc
END
不起作用,因为您将标准输入重定向到不关心它的东西,即赋值
export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A
有效,但其中有一个反向标记可能会阻止您使用它。另外,你真的应该避免使用反引号,最好使用命令替换符号
$(..)
.
export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A
仍然没有保留换行符的解决方案。
这不是真的——你可能只是被 echo 的行为误导了:
echo $VAR # strips newlines
echo "$VAR" # preserves newlines
Branching off 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"''
数组是一个变量,所以在这种情况下 mapfile 可以工作
mapfile y <<'z'
abc'asdf"
$(dont-execute-this)
foo"bar"''
z
然后可以这样打印
printf %s "${y[@]}"
我不敢相信我是第一个发布这个的人。
@Erman 和@Zombo 很接近,但是
mapfile
不只是读取数组...
考虑一下:
#!/bin/bash
mapfile -d '' EXAMPLE << 'EOF'
Hello
こんにちは
今晩は
小夜なら
EOF
echo -n "$EXAMPLE"
产量:
你好 こんりちか 今晨ha 小夜なら
$''
是给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小夜なら'
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
)"
感谢 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
#!/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
我发现自己必须读取其中包含 NULL 的字符串,所以这里有一个解决方案,可以读取您扔给它的anything。尽管如果你真的在处理 NULL,你将需要在十六进制级别处理它。
$ 猫 > 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
这是一种(恕我直言)非常优雅并避免 UUOC 的方法:
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>
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT