Exibir stdout e stderr em dois fluxos separados

Exibir stdout e stderr em dois fluxos separados

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 screeno 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 screeno modo de cópia).

Se você executar um shell bashinterativamente 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, poisecobashtambé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 emacsou vicom set -o emacsou set -o vi.

Responder2

Esta é uma solução feia baseada no annotate-outputscript 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_scriptou 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 stdoutfluxo 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 stderrapó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 stderrsempre 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 ncursesfunciona, 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 stdoutfluxos stderre saberá antecipadamente a duração do linhas futuras adicionadas a ele para alinhá-las corretamente.

informação relacionada