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

the*_*fog 12 bash

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

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF
echo "$message"
Run Code Online (Sandbox Code Playgroud)

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

#!/bin/bash

set -o errexit

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

echo "$message"
Run Code Online (Sandbox Code Playgroud)

结果:(无)
但这确实有效

#!/bin/bash

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

echo "$message"
Run Code Online (Sandbox Code Playgroud)

结果

$ sudo ./script.sh 
this is a 
mulitline
message
Run Code Online (Sandbox Code Playgroud)
  • bash --version - GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu)

Bar*_* IO 17

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

  • 严格来说,它是`\0`,而不是空字符串。 (4认同)
  • @cuonglm,更准确地说,当一个空参数传递给 `-d` 时,分隔符是 `\0`(一个 NUL 字节)。一个特例主要是编码事故的结果(尽管 [`bash` 维护者不会承认它并解释为什么它没有记录](http://thread.gmane.org/gmane.comp.shells.bash.bugs /24988/focus=24993))。 (2认同)

小智 11

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

$ read -d '' message <<-_ThisMessageEnds_
>     this is a
>     multi line
>     message
> _ThisMessageEnds_
$ exitval=$?
$ echo "The exit val was $exitval"
The exit val was 1.
Run Code Online (Sandbox Code Playgroud)

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

read -d '' message <<-_ThisMessageEnds_ || echo "$message"
    this is a
    multi line
    message
_ThisMessageEnds_
Run Code Online (Sandbox Code Playgroud)

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

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

cat <<-_ThisMessageEnds_
    this is a
    mulitline
    message
_ThisMessageEnds_
Run Code Online (Sandbox Code Playgroud)

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

  • “没有执行读取命令(速度更快)”-&gt; read 是 bash 内置程序,cat 是外部程序,所以我怀疑 cat 更快(因为 fork-exec 是必要的)。 (3认同)

cuo*_*glm 7

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

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

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

$ while read -d '' line; do echo "$line"; done < <(printf '1')
Run Code Online (Sandbox Code Playgroud)

什么都不打印:

$ while read -d '' line; do echo "$line"; done < <(printf '1\0')
1
Run Code Online (Sandbox Code Playgroud)

给你1

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


Sté*_*las 7

read -d '' message
Run Code Online (Sandbox Code Playgroud)

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

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

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

在这里,您需要在您的 here-document 中包含一个 NUL 分隔符read才能成功返回。然而bash,从 here-documents 中剥离 NUL 字符是不可能的(这至少比yash剥离第一个 NUL 之后的所有内容要好,或者 ksh93 在 here-document 包含 NUL 时似乎进入无限循环)。

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

NUL=$'\0'
IFS= read -d $NUL -r var << EOF
1
2
3$NUL
EOF
Run Code Online (Sandbox Code Playgroud)

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

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

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

ONE=$'\1'
IFS= read -d "$ONE" -r var << EOF
1
2
3$ONE
EOF
Run Code Online (Sandbox Code Playgroud)

但你也可以这样做:

var=$(cat <<EOF
message
here
EOF
)
Run Code Online (Sandbox Code Playgroud)

那仍然不允许使用 NUL 字符。然而,这是标准代码,因此您无需依赖特定于 zsh/bash 的read -d. 另外请注意,它会删除所有尾随换行符,并且除了ksh93cat启用内建的,这意味着产卵额外的过程和命令。

  • @cuonglm,另请参阅 [bash 邮件列表上的这个最近线程](http://thread.gmane.org/gmane.comp.shells.bash.bugs/24988/focus=24992) (2认同)