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

How to assign a heredoc value to a variable in Bash?

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

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

如何使用bash中的heredoc将其分配给变量?

我需要保留新行。

我不想逃避字符串中的字符,那会很烦人…


您可以避免使用cat,并更好地处理不匹配的引号:

1
2
3
4
5
$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

如果在回送变量时不引用它,新行将丢失。引用它可以保护它们:

1
2
3
4
$ echo"$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"'
'

如果要在源代码中使用缩进来提高可读性,请在小于号后使用破折号。只能使用制表符(无空格)进行缩进。

1
2
3
4
5
6
7
8
9
$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"'
'
    EOF
$ echo"$VAR"
abc'
asdf"
$(dont-execute-this)
foo"
bar"''

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

1
2
3
4
5
6
7
8
9
$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF

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

通过按ctrl vbkbd t,可以在命令行插入选项卡。如果您使用的编辑器取决于哪个编辑器,它也可以工作,或者您可能需要关闭自动将制表符转换为空格的功能。


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

1
2
3
4
5
6
7
8
9
10
11
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

确保用单引号分隔起始和结束文档。

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

感谢@ephemient的回答。


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

函数定义:

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

用途:

1
2
3
4
5
6
7
define VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF


echo"$VAR"

享受

P.S.为不支持read -d的shell制作了一个"读循环"版本。应该与set -eu和不成对的背景信号一起工作,但测试不太好:

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


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

There is still no solution that preserves newlines.

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

echo $VAR # strips newlines

echo"$VAR" # preserves newlines


1
2
3
VAR=<<END
abc
END

不起作用,因为您正在将stdin重定向到不关心它的内容,即分配

1
2
3
4
5
6
export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END

` ; echo $A

有效,但有一个背后的抽搐,可能会阻止你使用这个。另外,您应该避免使用反勾号,最好使用命令替换符号$(..)

1
2
3
4
5
6
export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END

) ; echo $A


从Neil的答案出发,您通常根本不需要var,您可以以与变量相同的方式使用函数,而且它比内联或基于read的解决方案更容易读取。

1
2
3
4
5
6
7
8
9
10
11
12
$ 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可以工作

1
2
3
4
5
mapfile y <<z
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

然后你可以这样打印

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


将HereDoc值赋给变量

1
2
3
4
5
6
VAR="$(cat <<'VAREOF'
abc'asdf"

$(dont-execute-this)
foo"bar"''
VAREOF
)"

用作命令的参数

1
2
3
4
5
6
7
echo"$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"

$(dont-execute-this)
foo"bar"''
SQLEOF
)"


我发现自己必须读取一个包含空值的字符串,所以这里有一个解决方案,可以读取任何您向它抛出的内容。尽管您实际上处理的是空值,但您需要在十六进制级别处理它。

$cat>读.dd.sh

1
2
3
4
5
6
7
8
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 )
}

证明:

1
2
3
4
5
$ . 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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ 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


由于dimo414的回答,这显示了他伟大的解决方案是如何工作的,并且显示了文本中也可以轻松地包含引号和变量:

实例输出

1
2
3
4
5
6
$ ./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

试验室

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/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


1
2
3
4
5
6
7
8
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT

echo -e $MYTEXT