我正在尝试以 POSIX 兼容的方式将 HEREDOC 文本放入 shell 脚本变量中。我尝试像这样:
#!/bin/sh
NEWLINE="
"
read_heredoc2() {
while IFS="$NEWLINE" read -r read_heredoc_line; do
echo "${read_heredoc_line}"
done
}
read_heredoc2_result="$(read_heredoc2 <<'HEREDOC'
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
)"
echo "${read_heredoc2_result}"
这产生了以下错误:
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ | | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
以下方法有效,但我不喜欢使用随机输出变量它是多么笨拙:
#!/bin/sh
NEWLINE="
"
read_heredoc1() {
read_heredoc_first=1
read_heredoc_result=""
while IFS="$NEWLINE" read -r read_heredoc_line; do
if [ ${read_heredoc_first} -eq 1 ]; then
read_heredoc_result="${read_heredoc_line}"
read_heredoc_first=0
else
read_heredoc_result="${read_heredoc_result}${NEWLINE}${read_heredoc_line}"
fi
done
}
read_heredoc1 <<'HEREDOC'
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
echo "${read_heredoc_result}"
正确输出:
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
有任何想法吗?
答案1
问题是,在重击中,内部$( ... )
转义(和其他)序列被解析,即使定界符本身没有它们。由于\
转义了换行符,您会得到一条双线。您所看到的实际上是 Bash 中的解析问题 - 其他 shell 不会这样做。反引号在旧版本中也可能是一个问题。我有确认这是 Bash 中的一个错误,并将在以后的版本中修复。
您至少可以大大简化您的功能:
func() {
res=$(cat)
}
func <<'HEREDOC'
...
HEREDOC
如果您想选择输出变量,可以对其进行参数化:
func() {
eval "$1"'=$(cat)'
}
func res<<'HEREDOC'
...
HEREDOC
或者是一个相当丑陋的没有eval
:
{ res=$(cat) ; } <<'HEREDOC'
...
HEREDOC
需要{}
的是 ,而不是()
,以便变量之后仍然可用。
根据您执行此操作的频率以及目的,您可能更喜欢其中一个或另一个选项。最后一个是一次性的最简洁的。
如果您能够使用zsh
,您的原始命令替换+heredoc将按原样工作,但您也可以进一步折叠所有这些:
x=$(<<'EOT'
...
EOT
)
Bash 不支持这一点,我认为任何其他 shell 也不会遇到您遇到的问题。
答案2
关于OP解决方案:
如果允许使用某些常量变量,则不需要 eval 来分配变量。
还可以实现调用接收 HEREDOC 的函数的一般结构。
在所有(合理的)shell 中都可以解决这两个问题的解决方案是这样的:
#!/bin/bash
nl="
"
read_heredoc(){
var=""
while IFS="$nl" read -r line; do
var="$var$line$nl"
done
}
read_heredoc <<'HEREDOC'
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
read_heredoc2_result="$str"
printf '%s' "${read_heredoc2_result}"
原问题的解决方案。
自 bash 2.04(以及最近的 zsh、lksh、mksh)以来一直有效的解决方案。
请参阅下面的更便携的 (POSIX) 版本。
#!/bin/bash
read_heredoc() {
IFS='' read -d '' -r var <<'HEREDOC'
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
}
read_heredoc
echo "$var"
核心命令
IFS='' read -d '' -r var <<'HEREDOC'
工作原理如下:
- 该词
HEREDOC
被(单)引用以避免对后续文本进行任何扩展。 - “here doc”内容在标准输入中以
<<
. - 该选项
-d ''
强制read
读取“此处文档”的全部内容。 - 该
-r
选项避免解释反斜杠引用的字符。 - 核心命令类似于
read var
. - 最后一个细节是
IFS=''
,这将避免读取删除默认 IFS 中的前导或尾随字符:spacetabnewline。
在 ksh 中,该选项的 null 值-d ''
不起作用。
作为解决方法,如果文本没有“回车符”,则 a-d $'\r'
有效($'\r'
当然,如果将 a 添加到每行末尾)。
添加的(在评论中)要求是生成符合 POSIX 的解决方案。
POSIX
扩展这个想法,使其仅使用 POSIX 选项运行。
这意味着主要没有-d
for read
。这会强制读取每一行。
这反过来又迫使需要一次捕获一条线。
然后,要构建var
尾随新行,必须添加(因为读取将其删除)。
#!/bin/sh
nl='
'
read_heredoc() {
unset var
while IFS="$nl" read -r line; do
var="$var$line$nl"
done <<\HEREDOC
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
}
read_heredoc
printf '%s' "$var"
这在所有合理的 shell 中都有效(并且已经过测试)。
答案3
为了支持尾随换行符,我结合了 @MichaelHomer 的答案和我原来的解决方案。我没有使用 @EliahKagan 指出的链接中建议的解决方法,因为第一个使用神奇字符串,而后两个不符合 POSIX 标准。
#!/bin/sh
NEWLINE="
"
read_heredoc() {
read_heredoc_result=""
while IFS="${NEWLINE}" read -r read_heredoc_line; do
read_heredoc_result="${read_heredoc_result}${read_heredoc_line}${NEWLINE}"
done
eval $1'=${read_heredoc_result}'
}
read_heredoc heredoc_str <<'HEREDOC'
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
echo "${heredoc_str}"
答案4
无用的使用 cat (引号 \ 和 `):
myplaceonline="
_ _ _
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | (_)_ __ ___
| '_ \` _ \\| | | | '_ \\| |/ _\` |/ __/ _ \\/ _ \\| '_ \\| | | '_ \\ / _ \\
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\\__, | .__/|_|\\__,_|\\___\\___|\\___/|_| |_|_|_|_| |_|\\___|
|___/|_
"
或者不加引号:
myplaceonline="$(figlet myplaceonline)"