Como acrescento uma string à saída do programa sem esperar pela linha inteira?

Como acrescento uma string à saída do programa sem esperar pela linha inteira?

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 sedassim:

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 | sedpeç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 sedsó 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 readbuiltin de bashpode ler entrada caractere por caractere. A parte read -d'' -s -N 1 chardesativa o delimitador -d'', ativa o modo silencioso -se lê apenas 1 caractere por vez -N 1na variável $char. Então o comando verifica se a variável $xexiste. 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 $xcomo 1e 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; }

informação relacionada