echo that outputs to stderr

echo that outputs to stderr

技术背景

在Bash脚本编程中,有时需要将echo命令的输出定向到标准错误输出(stderr),而不是标准输出(stdout)。这在处理错误信息、调试信息时非常有用,因为标准错误输出可以独立于标准输出进行重定向,方便将错误信息和正常输出信息分开处理。

实现步骤

1. 直接重定向

可以使用>&2echo的输出重定向到stderr:

1
>&2 echo "error"

>&2的作用是将文件描述符1(stdout)复制到文件描述符2(stderr),从而使输出定向到stderr。

2. 定义函数

简单函数

1
2
echoerr() { echo "$@" 1>&2; }
echoerr hello world

这种方式比脚本更快,且没有依赖。

使用here string的函数

1
echoerr() { cat <<< "$@" 1>&2; }

该函数可以打印传递给它的任何内容,包括echo通常会忽略的参数(如-n)。

使用printf的函数

1
echoerr() { printf "%s\n" "$*" >&2; }

同样避免了参数被忽略的问题。

3. 多行错误输出

1
2
3
4
5
{
echo "First error line"
echo "Second error line"
echo "Third error line"
} >&2

这种方式使用Bash内置命令,使多行错误输出更不容易出错。

4. 日志记录到syslog

1
logger -s $msg

-s选项表示将消息同时输出到标准错误和系统日志。

5. 其他方式

1
echo "my errz" >> /proc/self/fd/2

1
echo "my errz" >> /dev/stderr

/proc/self是指向当前进程的链接,/proc/self/fd保存着进程打开的文件描述符,012分别代表stdinstdoutstderr

6. 添加ANSI颜色代码

1
2
3
4
echoerr() { printf "\e[31;1m%s\e[0m\n" "$*" >&2; }
# if somehow \e is not working on your terminal, use \u001b instead
# echoerr() { printf "\u001b[31;1m%s\u001b[0m\n" "$*" >&2; }
echoerr "This error message should be RED"

可以将错误信息以红色显示。

7. 重定向管道输入到stderr

1
2
3
4
5
6
7
8
9
10
function STDERR () {
cat - 1>&2
}

# remove the directory /bubu
if rm /bubu 2>/dev/null; then
echo "Bubu is gone."
else
echo "Has anyone seen Bubu?" | STDERR
fi

8. 使用read命令

1
read -t 0.1 -p "This will be sent to stderr"

-t 0.1是一个超时选项,用于禁用read将标准输入的一行存储到变量的主要功能。

9. 检查命令退出状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
or_exit() {
local exit_status=$?
local message=$*

if [ "$exit_status" -gt 0 ]
then
echo "$(date '+%F %T') [$(basename "$0" .sh)] [ERROR] $message" >&2
exit "$exit_status"
fi
}

gzip "$data_dir"
or_exit "Cannot gzip $data_dir"

rm -rf "$junk"
or_exit Cannot remove $junk folder

该函数用于检查上一个命令的退出状态,如果状态码大于0,则输出错误信息并终止脚本。

核心代码

定义echoerr函数

1
echoerr() { printf "%s\n" "$*" >&2; }

检查命令退出状态的函数

1
2
3
4
5
6
7
8
9
10
or_exit() {
local exit_status=$?
local message=$*

if [ "$exit_status" -gt 0 ]
then
echo "$(date '+%F %T') [$(basename "$0" .sh)] [ERROR] $message" >&2
exit "$exit_status"
fi
}

最佳实践

  • 使用函数封装错误输出逻辑,提高代码的可读性和可维护性。
  • 在需要同时记录错误信息到系统日志和标准错误输出时,使用logger -s命令。
  • 对于多行错误输出,使用代码块重定向,避免每行都添加重定向符号。
  • 在脚本中使用or_exit函数检查命令的退出状态,方便调试和错误处理。

常见问题

1. /proc/self在MacOS上不工作

/proc/self链接在MacOS上不可用,但在Android的Termux上,/proc/self/fd/*可用,而/dev/stderr不可用。可以参考如何从Bash中检测操作系统来使脚本更具可移植性。

2. cat的使用问题

不要使用cat来重定向输出,因为cat是一个外部程序,而echoprintf是Bash内置命令。启动外部程序会创建新的进程,带来额外的开销。使用内置命令和函数则更高效。