Bash 管道命令链,每个命令将状态输出为标准错误
我在bash脚本中有一系列管道命令,将标准输出管道化为标准输入:Bash 管道命令链,每个命令将状态输出为标准错误,bash,pipe,stdout,stdin,stderr,Bash,Pipe,Stdout,Stdin,Stderr,我在bash脚本中有一系列管道命令,将标准输出管道化为标准输入: prog1 | prog2 | prog3 它们各自输出一些标准误差。有些输出覆盖了前一行,有些不覆盖,有些两者都覆盖:例如,输出几行输出,然后在shell中有一个更新的“状态栏”。例如,curl可以将下载进度作为状态栏输出 由于状态栏可能在一个进程的输出和另一个进程的输出之间闪烁,因此输出相当不清楚 是否有办法使各种输出更清晰,例如 要弄清楚输出行来自链中的哪个程序 使所有状态栏同时可见而不闪烁 闪烁的示例: 试试这个:
prog1 | prog2 | prog3
它们各自输出一些标准误差。有些输出覆盖了前一行,有些不覆盖,有些两者都覆盖:例如,输出几行输出,然后在shell中有一个更新的“状态栏”。例如,curl可以将下载进度作为状态栏输出
由于状态栏可能在一个进程的输出和另一个进程的输出之间闪烁,因此输出相当不清楚
是否有办法使各种输出更清晰,例如
- 要弄清楚输出行来自链中的哪个程序
- 使所有状态栏同时可见而不闪烁
cat-v
it即可stdbuf -oL prog1 2> >(sed 's/\r//g; s/^/prog1: /' >&2) |
stdbuf -oL prog2 2> >(stdbuf -oL tr '\r' '\n' | sed 's/^/prog2: /' >&2) |
stdbuf -oL prog3 2> >(sed 's/\r//g; s/^/prog3: /' >&2) |
stdbuf -oL sed 's/\r//g; s/^/out: /'
对于任何更复杂的情况,如果您确实需要为多个进程共享屏幕(并且您正在以交互方式运行命令),请使用screen
或tmux
或类似工具通过多个进程共享屏幕,或编写您自己的应用程序来处理终端:
tmpd=$(mktemp -d)
mkfifo "$tmpd"/1 "$tmpd"/2
trap 'rm -r "$tmpd"' EXIT
# prog1 = seq 5
# prog2 = grep -v 3
# prog3 = cat
tmux new-session \; \
send-keys "seq 5 > $tmpd/1" C-m \; \
split-window -v \; \
send-keys "grep -v 3 < $tmpd/1 > $tmpd/2" C-m \; \
split-window -v \; \
send-keys "cat < $tmpd/2" C-m \; \
select-layout even-vertical \;
更高级的版本可以使用
fifo
s和systemd
drop-in单元,这将允许对每个可执行文件的执行进行真正的微调。行覆盖行为可能是这些程序中的一个或多个将\r
字符写入stderr。下面是一个简单的示例,您可以尝试:
$ progress() {
for i in {1..10}; do
printf "$1\r" "$i" >&2; sleep 1
done
echo >&2
}
$ progress 'Num: %s'
# Should display a single line, `Num: N`, with `N` incrementing from 1-10
还有其他方法可以控制光标,例如某些方法,但是\r
是最简单的实现方法。不幸的是,正如您所发现的,当多个程序竞争这一行时,或者如果同时写入\n
字符时,这种行为没有多大帮助:
$ ({ sleep $(( 1+(RANDOM%8) )); echo 'Interrupt!'; } & ) &&
progress 'Num %s' | progress '%s Something Else'
# Should see "flickering" between the two progress tasks, and eventually an "interruption"
不幸的是,没有通用的方法来禁用此行为,因为每个程序都独立地打印\r
字符,并且它们彼此都不知道。正是由于这个原因,许多程序都有某种机制来禁用这种进度样式的输出,因此首先要查找的是一个标志或关闭它的设置,如--no_progress
标志
如果这些是您编写或可以更改的程序,则可以检查该程序是否连接到TTY。在Bash中,这可以通过完成,它可能看起来像这样:
$ progress() {
for i in {1..10}; do
# Only print progress to stderr if stdout *and* stderr are attached to TTYs
if [[ -t 1 ]] && [[ -t 2 ]]; then
printf "$1\r" "$i" >&2; sleep 1
fi
done
echo >&2
}
如果这两种方法都不可行,最后一种选择是包装程序并预处理其输出(或者干脆用2>/dev/null
抑制stderr)。由于您希望同时保留stdout和stderr,这有点麻烦,但可以做到。您的助手将清理stderr,例如通过删除\r
字符,然后将它们交换回来。下面是一个例子:
# Wraps a given command, replacing CR characters on stderr with newlines
$ no_CRs() {
{ "$@" 3>&1 1>&2 2>&3 | tr '\r' '\n'; } 3>&1 1>&2 2>&3
}
$ no_CRs progress 'Num %s' | no_CRs progress '%s Something Else'
# Should print both program's stderr on separate lines, as \r is no longer being emitted
对于这个具有挑战性的问题,这里给出了一些有趣的想法,但到目前为止,我还没有看到任何完整的解决方案。我会试着给你一个。为了实现这一点,我首先编写了三个脚本,对应于PO所说的管道
prog1 | prog2 | prog3
prog1在错误流上生成由\n
分隔的消息,并在标准流上生成数字:
#/bin/bash
cmd=$(basename$0)
序号8|
而((i++<10));做
读线| |断开
echo-e“$cmd:message$i to stderr”>&2
回音$线
睡眠1
完成
echo-e“$clearline$cmd:没有更多输入”>&2
prog2生成由\r
分隔的消息,并在错误流上覆盖其自身的输出,并将数字从标准输入流传输到标准输出流:
#/bin/bash
cmd=$(basename$0)
el=$(t输出el)
而((i++<10));做
读线| |断开
echo-en“$cmd:message$i to stderr${el}\r”>&2
回音$线
睡眠2
完成
echo-en“$clearline$cmd:没有更多输入${el}\r”>&2
最后,prog3以与prog2
相同的方式读取标准输入流并将消息写入错误流:
#/bin/bash
cmd=$(basename$0)
el=$(t输出el)
而((i++<10));做
读线| |断开
echo-en“$cmd:message$i to stderr${el}\r”>&2
睡眠3
完成
echo-en“$clearline$cmd:没有更多输入${el}\r”>&2
而不是将这三个脚本作为
prog1 | prog2 | prog3
我们需要一个脚本来调用这三个程序,将错误流重定向到三个FIFO特殊文件(命名管道),但在启动此命令之前,我们必须先创建三个特殊文件,并在后台启动进程来侦听特殊文件:每次发送一整行,这些进程将把它打印在屏幕的一个特殊区域,我称之为任务栏
三个任务栏位于屏幕底部:上面的任务栏将包含错误流的prog1
消息,下一个任务栏将对应于prog2
,底部的最后一个任务栏将包含来自prog3
的消息
最后,必须删除FIFO文件
现在是棘手的部分:
\r
结尾的行的实用程序读取,因此在将消息行打印到屏幕之前,我必须将\r
更改为\n
李>
$ progress() {
for i in {1..10}; do
# Only print progress to stderr if stdout *and* stderr are attached to TTYs
if [[ -t 1 ]] && [[ -t 2 ]]; then
printf "$1\r" "$i" >&2; sleep 1
fi
done
echo >&2
}
# Wraps a given command, replacing CR characters on stderr with newlines
$ no_CRs() {
{ "$@" 3>&1 1>&2 2>&3 | tr '\r' '\n'; } 3>&1 1>&2 2>&3
}
$ no_CRs progress 'Num %s' | no_CRs progress '%s Something Else'
# Should print both program's stderr on separate lines, as \r is no longer being emitted