Como prefixar uma string para programar a saída sem esperar pela linha inteira?

5

Eu tenho um script que executa um comando em um servidor remoto usando SSH. Eu quero prefixar a string Remote: para 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:

$ myproject-db-push my_database_name
Exporting from database... Done
Archiving data... Done
Uploading archive to remote... Done
Running install script on remote
Remote: Decompressing archive into temporary directory... Done
Remote: Using database: my_database_name
Remote: Dropping collections:
Remote:  - my_collection_foo
Remote:  - my_collection_bar
Remote: Importing new data... Done

Neste caso, estou usando sed assim:

echo "$INSTALLCMD" | ssh -T "deploy@$SERVER" | sed -u "s/^/Remote: /"

O problema é, como expliquei, que nenhuma linha parcial é impressa na tela. Se eu remover a parte | sed , ela funcionará como esperado. Primeiro isso está escrito:

Importing new data... 

E alguns segundos depois, a linha está completa:

Importing new data... Done

Estou assumindo que sed só é capaz de trabalhar linha por linha. Eu tentei configurá-lo sem buffer, mas ele ainda espera por linhas inteiras. Existe outra maneira de conseguir isso?

    
por Hubro 23.01.2015 / 13:15

1 resposta

2

É um pouco complicado, porque todas essas utilidades ( sed , awk , grep ) são armazenadas em buffer de linha. Isso significa que eles imprimem a saída somente quando a linha termina (uma nova linha foi aprovada). 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, imprime first task: e após 2 segundos done . Tente você mesmo copiando-o no seu terminal.

Solução:

Adicione o seguinte ao 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 incorporado em bash pode ler caracteres de entrada 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 . Em seguida, 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. Desativar $x . Em seguida, a última instrução verifica se o caractere é uma nova linha. Se for uma nova linha, defina $x a 1 e, no próximo loop, o "prefixo" será impresso.

A coisa toda pode ser testada, quando você concatente as duas seqüê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; }
    
por 23.01.2015 / 14:41