¿Cómo antepongo una cadena a la salida del programa sin esperar toda la línea?

¿Cómo antepongo una cadena a la salida del programa sin esperar toda la línea?

Tengo un script que ejecuta un comando en un servidor remoto usando SSH. Quiero anteponer la cadena Remote:a cada línea de la salida, pero no quiero que cada línea se retrase hasta que toda la línea esté disponible. Aquí está el resultado de mi comando:

$ miproyecto-db-push mi_nombre_base_de_datos
Exportando desde la base de datos... Listo
Archivando datos... Listo
Subiendo archivo al control remoto... Listo
Ejecutando el script de instalación en forma remota
Remoto: Descomprimiendo el archivo en un directorio temporal... Listo
Remoto: Usando la base de datos: my_database_name
Remoto: Eliminación de colecciones:
Remoto: - my_collection_foo
Remoto: - my_collection_bar
Remoto: Importando nuevos datos... Listo

En este caso, estoy usando sedasí:

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

El problema es, como ya he explicado, que noparcialLas líneas se imprimen en la pantalla. Si quito la | sedpieza, funciona como se esperaba. Primero se escribe esto:

Importando nuevos datos...

Y unos segundos después, se completa la línea:

Importando nuevos datos... Listo

Supongo sedque solo puede funcionar línea por línea. Intenté configurarlo sin búfer, pero todavía espera líneas completas. ¿Hay otra manera de lograr esto?

Respuesta1

Es un poco complicado, porque todas esas utilidades ( sed,, ) están almacenadas en un búfer de línea awk. grepEso significa que imprimen el resultado solo cuando la línea ha terminado (ha aparecido una nueva línea). No pueden leer la entrada carácter por carácter.

Entonces, para probar, hice una pequeña secuencia que simula tu comportamiento:

{ 
  echo -n "first task: "
  sleep 2
  echo "done"
  echo -n "second task: "
  sleep 2
  echo "done"
}

Como en tu pregunta, se imprime first task:y después de 2 segundos done. Pruébelo usted mismo copiándolo en su terminal.

Solución:

Agregue lo siguiente detrás de su 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; }

Explicación:

La readfunción incorporada bashpuede leer la entrada carácter por carácter. La parte read -d'' -s -N 1 chardesactiva el delimitador -d'', activa el modo silencioso -sy lee sólo 1 carácter por vez -N 1en la variable $char. Luego el comando verifica si la variable $xexiste. En caso afirmativo, estamos en una nueva línea e imprimimos el "prefijo". Luego imprime el personaje. Desarmado $x. Luego, la última declaración verifica si el carácter es una nueva línea. Si se trata de una nueva línea establecida $xen 1y en el siguiente bucle, se imprimirá el "prefijo".

Todo se puede probar cuando concatenas las dos secuencias:

{ 
  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; }

información relacionada