2

我希望它如何工作:

  • 如果没有错误被 bash 捕获(如果没有返回非零退出代码 [除非被 || true 覆盖]),请保持沉默。隐藏标准输出和标准错误。
  • 当错误被 bash 捕获时,要详细。编写标准输出和标准错误。

在我的脚本中,只有 stdout 和 stderr 丢失了。

#!/bin/bash

exec 5>&1 >/dev/null
exec 6>&2 2>/dev/null

error_handler() {
   local return_code="$?"
   local last_err="$BASH_COMMAND"

   local stdout= # How to read FD 5?

   local stderr= # How to read FD 6?

   exec 1>&5
   exec 2>&6

   echo "ERROR!   
scriptname: $0
BASH_COMMAND: $last_err
\$?: $return_code
stdout: $stdout
stderr: $stderr
" 1>&2

   exit 1
} 

trap "error_handler" ERR

echo "Some message..."

# Some command fails, i.e. return a non-zero exit code.

mkdir

我可能会将 stdout/stderr 重定向到一个临时文件并使用 cat 来显示它,以防错误被 bash 捕获。如果不需要该临时文件,会更好一些。任何想法?

信用:这个问题的灵感来自问题How to undo exec > /dev/null in bash? 查尔斯·达菲回答

4

3 回答 3

3

让我们仔细看一下 I/O 重定向:

exec 5>&1  >/dev/null
exec 6>&2 2>/dev/null

我们看到文件描述符 5 是原始标准输出的副本,但标准输出将变为/dev/null. 同样,6 是标准误差的副本,但标准误差将变为/dev/null.

现在让我们考虑一下运行时会发生什么:

ls -l /dev/null /dev/not-actually/there

ls命令将输出写入/dev/nullto,/dev/null因为这是其标准输出的目标。同样,它会将不存在文件的错误写入其中/dev/not-actually/there/dev/null因为这是其标准错误所在的位置。

因此,命令的标准输出和标准错误都将不可挽回地丢失。

鉴于表达的要求,不会有一个简单的解决方案。您最好的选择可能是将标准输出和标准错误重定向到同一个文件(但请注意,错误和正常输出的交错可能不同,因为输出是一个文件)。或者,您可以将标准输出和标准错误定向到两个单独的文件,并在必要时显示它们。

请注意,您需要考虑在每个命令之后清空输出文件(让trap报告在文件被清空之前报告内容),这样您就不会报告命令的标准输出或标准错误 1 -9 当命令 10 失败时。

巧妙地做到这一点并正确处理管道等并非易事。我不确定是否建议一个传递命令和参数的函数(对于管道来说很棘手)或其他一些技术。

我在运行脚本中使用了“在一个文件中捕获所有内容”技术,该cron脚本会在适当的时候通过邮件发送输出。它并不完全令人满意,但总比完全没有错误消息要好得多。

您可以考虑使用expect和/或伪 tty,但做好工作将非常困难。

于 2013-07-27T02:45:23.607 回答
2

您的文件描述符 5 和 6 是只写的。shell 无法读取自己的输出。双向管道是等待发生的死锁,即使两端的进程不同。

我会采用临时文件的想法。

于 2013-07-27T02:50:22.377 回答
0

I/O 文件的实际路径对 shell 和其他应用程序是隐藏的;你需要一个知道如何挖掘细节的程序。如果您的系统支持, lsof可能会帮助您。尝试在错误例程中添加以下内容:

本地名称0="$(basename "$0")";
lsof -p$$ -d5,6 2>/dev/null |
    egrep "^${name0:0:5}[^ ]* +[^ ]+ +[^ ]+ +[56][a-zA-Z]* "

这将需要进行一些调整以使其变得健壮(短程序名称、带有空格的程序名称……),并且更友好(“4”的标准输出和“3”的“标准错误”……比如说替换程序名称在第 1 列)。但是当您调整时,请注意您可能会遇到的输出格式的巨大差异。不仅在系统之间,而且在同一系统上的不同文件类型之间。我把这个留给学生练习。

于 2013-07-27T02:03:07.737 回答