Display stdout and stderr in two separate streams
You could use GNU screen
's vertical split feature:
#! /bin/bash -
tmpdir=$(mktemp -d) || exit
trap 'rm -rf "$tmpdir"' EXIT INT TERM HUP
FIFO=$tmpdir/FIFO
mkfifo "$FIFO" || exit
conf=$tmpdir/conf
cat > "$conf" << 'EOF' || exit
split -v
focus
screen -t stderr sh -c 'tty > "$FIFO"; read done < "$FIFO"'
focus
screen -t stdout sh -c 'read tty < "$FIFO"; eval "$CMD" 2> "$tty"; echo "[Command exited with status $?, press enter to exit]"; read prompt; echo done > "$FIFO"'
EOF
CMD="$*"
export FIFO CMD
screen -mc "$conf"
To use for instance as:
that-script 'ls / /not-here'
The idea is that it runs screen with a temporary conf file that starts two screen windows in a vertical split layout. In the first one, we run your command with the stderr connected to the second one.
We use a named pipe for the second window to communicate its tty device to the first one, and also for the first one to tell the second one when the command is done.
The other advantage compared to pipe-based approaches is that the command's stdout and stderr are still connected to tty devices, so it doesn't affect the buffering. Both panes can also been scrolled up and down independently (using screen
's copy mode).
If you run a shell like bash
interactively with that script, you'll notice the prompt will be displayed on the second window, while the shell will read what you type in the first window as those shells output their prompt on stderr.
In the case of bash
, the echo of what you type will also appear on the second window as that echo is output by the shell (readline in the case of bash
) on stderr as well. With some other shells like ksh93
, it will show on the first window (echo output by the terminal device driver, not the shell), unless you put the shell in emacs
or vi
mode with set -o emacs
or set -o vi
.
This is an ugly solution based on the annotate-output
script of Debian ANNOTATE-OUTPUT(1).
Not sure if this is what you are looking for but could be something to start with:
#!/bin/bash
readonly col=150 # column to start error output
add_out ()
{
while IFS= read -r line; do
echo "$1: $line"
done
if [ ! -z "$line" ]; then
echo -n "$1: $line"
fi
}
add_err ()
{
while IFS= read -r line; do
printf "%*s %s %s: %s\n" $col "|" "$1" "$line"
done
if [ ! -z "$line" ]; then
printf "%*s %s: %s" $col "$1" "$line"
fi
}
cleanup() { __st=$?; rm -rf "$tmp"; exit $__st; }
trap cleanup 0
trap 'exit $?' 1 2 13 15
tmp=$(mktemp -d --tmpdir annotate.XXXXXX) || exit 1
OUT=$tmp/out
ERR=$tmp/err
mkfifo $OUT $ERR || exit 1
add_out OUTPUT < $OUT &
add_err ERROR < $ERR &
echo "I: Started $@"
"$@" > $OUT 2> $ERR ; EXIT=$?
rm -f $OUT $ERR
wait
echo "I: Finished with exitcode $EXIT"
exit $EXIT
You can test it using ./this_script another_script
or command
.
I will try to analyze the following part of your question:
would instead look something like this:
~$ some command some useful output info | more output | ERROR: an error another message | ERROR: has occurred ~$
If one wanted to break down what you want is :
1) The stdout
stream would not end each line with a CR LF
but instead with a '|' character. This would not align the two streams together of course, and alignment is out of the question because it would have to predict the length of future lines added to the stdout
, which is of course impossible.
2) Assuming we forget about alignment we would then simply output the stderr
after being processed by a pipeline that adds "ERROR:" to the beginning of each line. I suppose this is quite easy by making a simple script and make sure the stderr
always comes out through this script.
But that would create an output like this:
~$ some command some useful output info| more output| ERROR: an error another message| ERROR: has occurred
Which is not really helpful, is it?
Also I do not believe, it's what's you are after too!
The problem with the initial question, I think is that you do not take into account the serial nature of each line appended in a stream, in connection to the fact that both streams may be written asynchronously.
I believe that the closest possible solution would be to use ncurses
.
See.
[http://www.tldp.org/HOWTO/html_single/NCURSES-Programming-HOWTO/]
[http://invisible-island.net/ncurses/ncurses-intro.html#updating]
In order to do what you are after you need to buffer both streams and combine them to produce a third buffer that takes elements from both buffers. Then dump the third buffer into the terminal screen by erasing the terminal screen and repainting it each time the third buffer changes. But this is the way ncurses
works, so why reinvent the wheel and not take it up from there?
In any case, you would have to take over the way that the terminal screen is painted entirely! And realign the text in the reprinted version of the screen as you like. Much like a video game with terminal characters.
I hope my answer will be helpful in clarifying the limitations of what you are after...
Excuse me for repeating this but the biggest problem with what you showed is how will the "processor" of the stdout
and stderr
streams know in advance the length of the future lines added to it in order to align them properly.