Bash 中退出状态不为 0 时将 STDOUT 重定向到 STDERR

Redirect STDOUT to STDERR when exit status isn't 0 in Bash

编程语言 Crystal 当前不会在编译时失败时写入 STDERR。如果退出状态不是 0 并且仍然 return 退出状态,我需要将 STDOUT 重定向到 STDERR。

从字面上看,您的请求实际上是不可能的:重定向是在命令开始之前执行的,而退出状态只有在退出时才知道。

但是,您可以无条件地重定向到缓冲区,然后在知道退出状态后将该缓冲区写入标准输出或标准错误。

考虑类似于以下内容的包装器:

#!/bin/sh
if output=$("$@"); then
  printf '%s\n' "$output"
else
  retval=$?
  printf '%s\n' "$output" >&2
  exit "$retval"
fi

...调用为(如果保存为 stderr-wrapper):

stderr-wrapper your-program arg1 arg2 ...

迂回管道方法,需要tac和一个无害的eval,但既没有缓冲区变量,也没有临时文件。

演示脚本demo,使用echo false ; false模拟程序输出到stdout和returns一个错误代码“1".

#!/bin/bash
out=([0]=/dev/stdout [1]=/dev/stderr)
{ { echo  ;  ; echo $? ; } | tac ; } | \
  { read x ; eval tac \> ${out[$x]} ; exit $x ; }

证明,使用 annotate-output:

annotate-output +'' ./demo true ; echo --- ; annotate-output +'' ./demo false
 I: Started ./demo true
 O: true
 I: Finished with exitcode 0
---
 I: Started ./demo false
 E: false
 I: Finished with exitcode 1

demo 概括为 stderr-wrapper:

  1. 对于bash:

    #!/bin/bash
    # Usage:  stderr-wrapper program [ args... ]
    out=([0]=/dev/stdout [1]=/dev/stderr)
    { { $@ ; echo $? ; } | tac ; } | \
    { read x ; eval tac \> ${out[$((x>0))]} ; exit $x ; }
    
  2. 对于POSIX shell,(这里是dash):

    #!/bin/dash
    # Usage:  stderr-wrapper program [ args... ]
    { { "$@" ; echo $? ; } | tac ; } | \
    { read x ; eval tac 1\>\&$(((x>0)+1)) ; exit $x ; }
    

bash 版本的工作原理:

  1. 运行 有问题的程序,输出到 stdout.
  2. 之后立即打印错误代码,也输出到 stdout
  3. tac反转整个流,所以错误码在前。 (如果流很长,则价格昂贵。)
  4. read一行,错误码,存放在$x.
  5. eval $out 数组成员对应于输出应该去的设备,然后用 tac 重新反转输出到那个设备。