Tenho um script que executa um comando em um servidor remoto usando SSH. Quero acrescentar a string Remote:
a cada linha da saída, mas não quero que cada linha seja atrasada até que toda a linha esteja disponível. Aqui está a saída do meu comando:
$ meuprojeto-db-push meu_nome_do_banco_de_dados Exportando do banco de dados... Concluído Arquivando dados... Concluído Fazendo upload do arquivo para o controle remoto... Concluído Executando script de instalação remotamente Remoto: Descompactando arquivo em diretório temporário... Concluído Remoto: Usando banco de dados: my_database_name Remoto: Descartando coleções: Remoto: - minha_coleção_foo Remoto: - minha_coleção_bar Remoto: Importando novos dados... Concluído
Neste caso, estou usando sed
assim:
echo "$INSTALLCMD" | ssh -T "deploy@$SERVER" | sed -u "s/^/Remote: /"
O problema é, como expliquei, que nãoparciallinhas são impressas na tela. Se eu remover a | sed
peça, ela funcionará conforme o esperado. Primeiro está escrito isto:
Importando novos dados...
E alguns segundos depois, a linha está completa:
Importando novos dados... Concluído
Presumo que sed
só seja capaz de trabalhar linha por linha. Tentei configurá-lo como sem buffer, mas ele ainda aguarda linhas inteiras. Existe outra maneira de conseguir isso?
Responder1
É um pouco complicado, porque todos esses utilitários ( sed
, awk
, grep
) são armazenados em buffer de linha. Isso significa que eles imprimem a saída somente quando a linha termina (uma nova linha é exibida). Eles não podem ler o caractere de entrada por caractere.
Então para testar fiz uma pequena sequência, que simula seu comportamento:
{
echo -n "first task: "
sleep 2
echo "done"
echo -n "second task: "
sleep 2
echo "done"
}
Como na sua pergunta, ele imprime first task:
e depois de 2 segundos done
. Experimente você mesmo, copiando-o em seu terminal.
Solução:
Adicione o seguinte atrás do seu comando:
IFS=
command | { x=1; while IFS= read -d'' -s -N 1 char; do
[ $x ] && printf "Remote: "
printf "$char"
unset x
[ "$char" == "
" ] && x=1
done; }
Explicação:
O read
builtin de bash
pode ler entrada caractere por caractere. A parte read -d'' -s -N 1 char
desativa o delimitador -d''
, ativa o modo silencioso -s
e lê apenas 1 caractere por vez -N 1
na variável $char
. Então o comando verifica se a variável $x
existe. Se sim, estamos em uma nova linha e imprimimos o “prefixo”. Em seguida, imprima o personagem. Não definido $x
. A última instrução verifica se o caractere é uma nova linha. Se for uma nova linha definida $x
como 1
e no próximo loop, o "prefixo" será impresso.
A coisa toda pode ser testada, quando você concatena as duas sequências:
{
echo -n "first task: "
sleep 2
echo "done"
echo -n "second task: "
sleep 2
echo "done"
} | { x=1; while IFS= read -d'' -s -N 1 char; do
[ $x ] && printf "Remote: "
printf "$char"
unset x
[ "$char" == "
" ] && x=1
done; }