为什么 set-o errexit 会破坏这个 read/heredoc 表达式?

为什么 set-o errexit 会破坏这个 read/heredoc 表达式?

我一直在使用下面的模式在 bash 脚本中将多行消息打印到终端。

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF
echo "$message"

这一直有效 - 直到几天前该模式才停止工作。通过停止工作,我的意思是当 bash 在脚本中遇到这些定界符表达式时 - 它似乎什么也没做 - 没有输出。
我能想到的在过去几天发生的唯一变化是脚本运行的环境是 Ubuntu 14.04 live USB,而不是“完整”安装。
然后我发现,当我将heredoc移到脚本set -o errexit语句之前时,它又开始工作了。即这不起作用

#!/bin/bash

set -o errexit

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF

echo "$message"

结果:(什么也没有)
但是这个确实有效

#!/bin/bash

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF

echo "$message"

结果

$ sudo ./script.sh 
this is a 
mulitline
message
  • bash --版本-GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu)

答案1

read如果找不到分隔符,则返回非零退出状态。将分隔符设置为空字符串后,它使用 NUL 字节作为分隔符,而这些字节通常在文本文件中找不到。

答案2

当到达文件结尾 (EOF) 标记时,读取命令的退出代码为 1。在这种特殊情况下,当分隔-d符为空时,这种情况总是会发生'',其中源流是不能包含 \0 的定界文档。

$ read -d '' message <<-_ThisMessageEnds_
>     this is a
>     multi line
>     message
> _ThisMessageEnds_
$ exitval=$?
$ echo "The exit val was $exitval"
The exit val was 1.

退出值是一个错误(不是 0),这使得可以使用 AND/OR 构造来避免脚本退出:

read -d '' message <<-_ThisMessageEnds_ || echo "$message"
    this is a
    multi line
    message
_ThisMessageEnds_

这会将消息发送到控制台,但避免使用 退出它errexit

但既然我们正在这条路上减少,为什么不直接使用它:

cat <<-_ThisMessageEnds_
    this is a
    mulitline
    message
_ThisMessageEnds_

不执行读取命令(速度更快),不需要变量,退出代码不会出错,需要维护的代码更少。

答案3

read -d '' message

读取标准输入,直到第一个未转义的(因为您没有添加-r)NUL 字符或输入的末尾,并将$IFS反斜杠字符处理后的数据存储到$message(不带分隔符)。

如果在输入中找不到未转义的分隔符,read则 的退出状态为非零。如果读取完整的终止记录,则仅返回 0(成功)。

它对于处理 NUL 分隔的记录(例如 的输出)最有用find -print0(尽管您随后需要IFS= read -rd '' record语法)。

在这里,您需要在此处文档中包含 NUL 分隔符才能read成功返回。然而bash,从此处文档中删除 NUL 字符是不可能的(这至少比yash删除第一个 NUL 之后的所有内容或 ksh93 更好,当此处文档包含 NUL 时,ksh93 似乎进入无限循环)。

zsh是唯一可以在其此处文档中包含 NUL 或将其存储在其变量中或将 NUL 字符作为参数传递给其内置函数/函数的 shell。在 中zsh,您可以执行以下操作:

NUL=$'\0'
IFS= read -d $NUL -r var << EOF
1
2
3$NUL
EOF

zsh也可以理解read -d ''为 NUL 分隔符,如bash.read -d $'\0'也适用,bash但它确实将空参数传递给readlike in,read -d ''因为bash在其命令行中不支持 NUL 字节)。

(请注意,之后有一个额外的换行符$NUL

在 中bash,您可以使用不同的字符:

ONE=$'\1'
IFS= read -d "$ONE" -r var << EOF
1
2
3$ONE
EOF

但你也可以这样做:

var=$(cat <<EOF
message
here
EOF
)

这仍然不允许 NUL 字符。然而,这是标准代码,因此您不需要依赖 zsh/bash 特定的read -d.另请注意,它会删除所有尾随换行符,除非启用ksh93内置cat功能,否则这意味着会生成额外的进程和命令。

答案4

当你使用set -o errexit并且你的脚本中断时,这意味着有问题。

在这里,是read,它无法正确读取您的输入。

在 中bash,当您使用 时read -d ''read内置将使用空字符\0作为行终止符。因此,当\0您的输入中没有内容时,read会将所有输入读取到message变量中,并返回非零退出状态以指示存在错误:

$ while read -d '' line; do echo "$line"; done < <(printf '1')

在以下情况下不打印任何内容:

$ while read -d '' line; do echo "$line"; done < <(printf '1\0')
1

给你1

read当它到达 EOF 时也会返回非零状态,但这用于指示当您readwhile循环一起使用时没有更多输入可读取,因此while可以终止循环。这与你的问题无关。

相关内容