Estou procurando uma maneira de separar visualmente stdout e stderr, para que não se intercalem e possam ser facilmente identificados. Idealmente, stdout e stderr teriam áreas separadas na tela em que são exibidos, por exemplo, em colunas diferentes. Por exemplo, uma saída que seria assim:
~$ some command
some useful output info
ERROR: an error
more output
ERROR: has occurred
another message
~$
em vez disso, seria algo assim:
~$ some command |
some useful output info |
more output | ERROR: an error
another message | ERROR: has occurred
~$ |
Responder1
Você poderia usar screen
o recurso de divisão vertical do GNU:
#! /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"
Para usar, por exemplo, como:
that-script 'ls / /not-here'
A idéia é que ele execute screen com um arquivo conf temporário que inicia duas janelas de tela em um layout dividido verticalmente. No primeiro, executamos seu comando com o stderr conectado ao segundo.
Usamos um pipe nomeado para a segunda janela comunicar seu dispositivo tty à primeira, e também para a primeira informar à segunda quando o comando for concluído.
A outra vantagem em comparação com abordagens baseadas em pipe é que o stdout e o stderr do comando ainda estão conectados aos dispositivos tty, portanto, isso não afeta o buffer. Ambos os painéis também podem ser rolados para cima e para baixo de forma independente (usando screen
o modo de cópia).
Se você executar um shell bash
interativamente com esse script, notará que o prompt será exibido na segunda janela, enquanto o shell lerá o que você digita na primeira janela, à medida que esses shells geram seu prompt no stderr.
No caso de bash
, oecodo que você digita também aparecerá na segunda janela, poisecobash
também é gerado pelo shell (readline no caso de ) no stderr. Com alguns outros shells como ksh93
, ele será exibido na primeira janela (ecosaída pelo driver de dispositivo de terminal, não pelo shell), a menos que você coloque o shell no modo emacs
ou vi
com set -o emacs
ou set -o vi
.
Responder2
Esta é uma solução feia baseada no annotate-output
script do DebianANOTAÇÃO-SAÍDA(1). Não tenho certeza se é isso que você está procurando, mas pode ser algo para começar:
#!/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
Você pode testá-lo usando ./this_script another_script
ou command
.
Responder3
Tentarei analisar a seguinte parte da sua pergunta:
em vez disso, seria algo assim:
~$ algum comando algumas informações úteis sobre saída | mais produção | ERRO: um erro outra mensagem | Um erro ocorreu ~$
Se alguém quiser detalhar o que você deseja é:
1) O stdout
fluxo não terminaria cada linha com um CR LF
, mas sim com um '|' personagem. É claro que isso não alinharia os dois fluxos, e o alinhamento está fora de questão porque seria necessário prever o comprimento das linhas futuras adicionadas ao stdout
, o que é obviamente impossível.
2) Supondo que esqueçamos o alinhamento, simplesmente geraríamos o arquivo stderr
após ser processado por um pipeline que adiciona "ERROR:" ao início de cada linha. Suponho que isso seja bastante fácil criando um script simples e garantindo que stderr
sempre saia por meio desse script.
Mas isso criaria uma saída como esta:
~$ algum comando algumas informações úteis sobre saída | mais produção | ERRO: um erro outra mensagem | Um erro ocorreu
O que não é realmente útil, não é? Também não acredito, é isso que você procura também!
Acho que o problema com a pergunta inicial é que você não leva em consideração a natureza serial de cada linha anexada a um fluxo, devido ao fato de que ambos os fluxos podem ser escritos de forma assíncrona.
Acredito que a solução mais próxima possível seria usar o ncurses
.
Ver.
[http://www.tldp.org/HOWTO/html_single/NCURSES-Programming-HOWTO/]
[http://invisible-island.net/ncurses/ncurses-intro.html#updating]
Para fazer o que você deseja, você precisa armazenar em buffer os dois fluxos e combiná-los para produzir um terceiro buffer que receba elementos de ambos os buffers. Em seguida, despeje o terceiro buffer na tela do terminal, apagando a tela do terminal e repintando-a cada vez que o terceiro buffer for alterado. Mas é assim que ncurses
funciona, então por que reinventar a roda e não começar a partir daí?
De qualquer forma, você teria queassumir a forma como a tela do terminal é totalmente pintada! E realinhe o texto na versão reimpressa da tela como desejar. Muito parecido com um videogame com personagens terminais.
Espero que minha resposta seja útil para esclarecer as limitações do que você procura...
Desculpe-me por repetir isso, mas o maior problema com o que você mostrou é como o "processador" dos stdout
fluxos stderr
e saberá antecipadamente a duração do linhas futuras adicionadas a ele para alinhá-las corretamente.