我正在尝试创建一个通用函数或脚本,可以将数据通过管道传输到其中,并在输出前面添加显示数据“到达”的 fd(stdout 或 stderr)。我的 Bash 技能充其量只是中等水平,而且我发现自己在试图理解诸如{ foo 2>&1 >&3 3>&- | bar 3>&-; } 3>&1
.
任何人都可以建议这是否可能以及实现的方法吗?
本质上我想做这样的事情:
$ { echo "foo" ; echo "bar" 1>&2 ; } | my_output_processor.sh
stdout: foo
stderr: bar
我读了管道 STDERR 与 STDOUT但这并没有为我解答。我认为答案是https://unix.stackexchange.com/a/440439/307184可能包含一条线索,但似乎只能在运行脚本中工作,而不能在数据管道中工作。
我的 macOS 上的 Bash 版本是 5.0。
编辑:@Glenn 下面的答案有效!我制作了几个小辅助函数来节省一些打字并防止错误,并进行了如下测试:
$ out(){ sed "s/^/out: /" ;} ; err(){ sed "s/^/err: /" 1>&2 ;}
$ { echo "good" ; echo "bad" >&2; } 2> >(err) 1> >(out)
err: bad
out: good
关于重定向顺序的后续问题:为什么2> >(err) 1> >(out)
效果很好,而效果1> >(out) 2> >(err)
却不佳? (更新:下面由 @RudiC 回答 - 辅助函数已更新,因此它们现在可以按任何顺序工作)
答案1
使用流程替代不是管道:
{ echo "foo" ; echo "bar" >&2; } 2> >(sed "s/^/err: /") > >(sed "s/^/out: /")
err: bar
out: foo
如果您在脚本中执行此操作,请使用以下exec
命令在脚本的整个持续时间内设置重定向:
bash -c '
exec 2> >(sed "s/^/err: /") > >(sed "s/^/out: /")
echo foo
echo bar >&2
'
err: bar
out: foo
如果你这样做
exec > >(sed "s/^/out: /") 2> >(sed "s/^/err: /")
那么输出是
out: foo
out: err: bar
我想这就是你所说的“不起作用”的意思。这是因为默认情况下“err”进程替换的输出会转到标准输出,而现在指向“out”进程子进程。你必须这样做才能解决它:
exec 3>&1 > >(sed "s/^/out: /") 2> >(sed "s/^/err: /" >&3)
创建另一个指向默认 stdout 的文件描述符 (3),然后 stderr 重定向打印到 fd3。
答案2
执行时后续问题会自行回答:
该err
函数也打印到标准输出。显然,第一种形式具有原始的标准输出,而第二种形式已经使用“重定向到 out() 函数”。
答案3
这应该适用于任何标准 shell,而不仅仅是 bash:
{ cmd 2>&1 >&3 | sed 's/^/err: /' >&2; } 3>&1 | sed 's/^/out: /'
为了方便起见,可以将其制成一个函数:
# usage louterr cmd [args ...]
louterr(){ { "$@" 2>&1 >&3 | sed 's/^/err: /' >&2; } 3>&1 | sed 's/^/out: /'; }
cmd(){ echo ERR >&2; echo OUT; }
louterr cmd
out: OUT
err: ERR
louterr louterr louterr cmd
err: err: err: ERR
out: out: out: OUT
或者,使用可配置的“过滤器”:
# usage: louterrx out_filter err_filter cmd [args ...]
louterrx(){
oflt=$1; eflt=$2; shift; shift
eval "{ \"\$@\" 2>&1 >&3 | $eflt >&2; } 3>&1 | $oflt"
}
louterrx "sed 's/^/out: /'" "sed 's/^/err: /'" cmd
这样做的缺点是退出状态cmd
将被隐藏(与管道的任何右侧一样)。要解决这个问题,您可以使用子 shell 和set -o pipefail
(在bash
、zsh
、ksh
等中受支持,但尚不支持dash
):
louterr()(
set -o pipefail
{ "$@" 2>&1 >&3 | sed 's/^/err: /' >&2; } 3>&1 | sed 's/^/out: /'
)
cmd(){ echo ERR >&2; echo OUT; return 13; }
louterr cmd; echo $?
out: OUT
err: ERR
13
我不建议> >(...)
在这种情况下使用 zsh/bash-ism,因为它依赖于与在不同 shell 中执行扩展和重定向的顺序相关的怪癖(并且受到许多其他问题的影响)怪癖和虫子)。例如,在
ls no_such_file >/dev/null 2> >(cat)
cat
( 的错误消息)的输出ls
将被重定向到/dev/null
inbash
但不是 in zsh
。
~必须使用该cmd > >(...)
形式的唯一情况是当cmd
是一个函数,其目的是修改当前环境并且不能在子 shell 中运行时。