Redirecionamento de stdout ignora linhas sem nova linha

3

Estou tentando que meu stderr seja impresso em vermelho no terminal. O script abaixo redireciona o 2 para um trap de depuração 8 personalizado.

exec 9>&2
exec 8> >(
    while IFS='' read -r line || [ -n "$line" ]; do
       echo -e "${RED}${line}${COLORRESET}"
    done
)
function undirect(){ exec 2>&9; } # reset to original 9 (==2)
function redirect(){ exec 2>&8; } # set to custom 8
trap "redirect;" DEBUG
PROMPT_COMMAND='undirect;'

Ele vem de aqui , com uma explicação clara.

Parece funcionar muito bem, no entanto, a entrada terminada sem nova linha não é impressa. Citando o autor gospes novamente:

bash> echo -en "hi\n" 1>&2
    hi       <-- this is red
bash> echo -en "hi" 1>&2
bash> echo -en "hi" 1>&2
bash> echo -en "hi\n" 1>&2
    hihihi   <-- this is red

Eu não consigo descobrir o porquê. O conteúdo não-newline parece acabar em algum tipo de buffer. Nem sequer alcança o descritor de arquivo 8 , ou de alguma forma não quer ser impresso imediatamente. Onde isso vai? redirect é chamado corretamente toda vez. Além disso, IFS='' significa que não há delimitador, então não entendo muito bem por que o eco em 8 acontece na linha.

Um bugfix seria muito apreciado, eu vinculei a resposta citada a essa pergunta.

Toda essa solução, como apontado por Gilles, não é perfeita. Estou tendo problemas com leitura, stdin, barras de progresso, não posso nem su nem source . E freqüentemente grandes problemas como canos quebrados e saídas de terminal inesperadas. Se alguém chegou aqui pela minha ligação, por favor, considere o uso de link em vez disso, é muito melhor (sem problemas ainda) (no entanto echo bla >&2 permanece não vermelho e o respectivo número está fechado )

    
por Blauhirn 27.05.2017 / 19:48

2 respostas

5

Você obteve a saída de linhas parciais, como parte da mesma linha no ponto em que a nova linha foi impressa. As partes da linha são armazenadas em buffer em read , isso é o que faz :

The read utility shall read a single logical line from standard input

Por exemplo, isso imprime <foobar> após um segundo, não <foo><bar> .

(echo -n foo ; sleep 1 ; echo bar) | (read x ; echo "<$x>")

Se você deseja capturar informações em partes menores do que linhas completas, será necessário fazer outra coisa, por exemplo, com Perl. Isso imprimiria <foo><bar\n> (com a nova linha antes do último > , já que ao contrário de read , Perl não lida com a nova linha final especialmente. Não importa com a coloração.)

(echo -n foo ; sleep 1 ; echo bar) | 
    perl -e '$|=1; while(sysread STDIN,$a,9999) { print "<$a>"}'

Se você tiver os códigos de controle para cores ( RED e COLORRESET ) exportados no ambiente, você pode usá-los no script Perl como aqui:

perl -e '$|=1; while(sysread STDIN,$a,9999) {print "$ENV{RED}$a$ENV{COLORRESET}"}'
    
por 27.05.2017 / 21:15
1

No Bash, você pode usar a opção -d para o read builtin, que define o símbolo do fim da linha. man bash declara isso:

-d delim    The first character of delim is used to terminate the input line, 
            rather than newline.

Se não estiver definido, read esperará que \n apareça para considerar uma string como uma linha. Mas quando você usa a opção -d , você pode definir NUL como o delimitador. Claro que você também precisa terminar a entrada NUL.

Exemplo:

printf "%s
x

y
z
" $'x\n' y z | while IFS='' read -r -d $'
printf "%s
x
yz(...)
" $'x\n' y z | while IFS='' read -r -d $'
-d delim    The first character of delim is used to terminate the input line, 
            rather than newline.
' line do printf "%s" "$line" done
' line do printf "%s\n" "$line" done

Saída:

printf "%s
x

y
z
" $'x\n' y z | while IFS='' read -r -d $'
printf "%s
x
yz(...)
" $'x\n' y z | while IFS='' read -r -d $'%pre%' line do printf "%s" "$line" done
' line do printf "%s\n" "$line" done

Mais uma vez, mas agora o printf no loop while não é compatível com \n .

%pre%

Saída:

%pre%

Eu adicionei o (...) e isso significa que não há fim de linha no final da segunda linha. Mas o texto ainda é processado e impresso.

    
por 27.05.2017 / 21:53