Comando para anteponer cadena a cada línea?


36

Buscando algo como esto? ¿Algunas ideas?

cmd | prepend "[ERRORS] "

[ERROR] line1 text
[ERROR] line2 text
[ERROR] line3 text
... etc

¿Hay alguna manera de configurar esto para todos los comandos en bash function / script?
Alexander Mills

Respuestas:


39
cmd | while read line; do echo "[ERROR] $line"; done

tiene la ventaja de usar solo bash builtins, por lo que se crearán / destruirán menos procesos, por lo que debería ser un toque más rápido que awk o sed.

@tzrik señala que también podría ser una buena función bash. Definiéndolo como:

function prepend() { while read line; do echo "${1}${line}"; done; }

permitiría que se use como:

cmd | prepend "[ERROR] "

44
En realidad, esto solo reduce el recuento de procesos en uno. (Pero podría ser más rápido porque no se utilizan expresiones regulares ( sed) o incluso división de cadenas ( awk).)
Grawity

Por cierto, tenía curiosidad sobre el rendimiento y aquí están los resultados de mi punto de referencia simple usando bash, sed y awk. Empujar alrededor de 1000 líneas de texto (salida dmesg) al archivo FIFO y luego leerlas así: pastebin.ca/1606844 Parece que awk es el ganador. Alguna idea de por qué?
Ilya Zakreuski el

1
tenga cuidado al ejecutar pruebas de tiempo como esa: intente ejecutarlas en los 6 diferentes pedidos y luego promedie los resultados. Diferentes órdenes para mitigar los efectos de caché de bloque y promedio para mitigar la interrupción de fondo / efectos de programación.
pjz

Esta pregunta está etiquetada como "shell", no "bash".
fiatjaf

1
Lo suficientemente fácil como para envolverlo en una función también:function prepend() { while read line; do echo "${1}${line}"; done; }
tzrlk

46

Prueba esto:

cmd | awk '{print "[ERROR] " $0}'

Aclamaciones


1
Esto tiene la desventaja de que "[ERROR]" no puede ser una variable, porque toda la expresión debe estar entre comillas simples.
user1071136

44
awk -vT="[ERROR] " '{ print T $0 }'oawk -vT="[ERROR]" '{ print T " " $0 }'
Tino

2
T="[ERROR] " awk '{ print ENVIRON["T"] $0 }'oT="[ERROR]" awk '{ print ENVIRON["T"] " " $0 }'
Tino

Simplemente puede salir del alcance de las comillas para desreferenciar la variable: cmd | awk '{print "['$V]' " $0}'- esto debe evaluarse una vez al comienzo, para que no haya sobrecarga de rendimiento.
Robert

13

Con todo el crédito debido a @grawity, envío su comentario como respuesta, ya que me parece la mejor respuesta.

sed 's/^/[ERROR] /' cmd

¿Por qué es esto preferible a la solución bash?
user14645

1
Supongo que depende de tu propósito. Si su objetivo es simplemente anteponer cada línea en un archivo, esto lo logra con muy pocos caracteres, utilizando una herramienta muy familiar. Prefiero eso a un script bash de 10 líneas. El awkone-liner es lo suficientemente agradable, pero creo que hay más personas con las sedque está familiarizado awk. El script bash es bueno para lo que hace, pero parece que está respondiendo una pregunta que no se hizo.
Eric Wilson el

La respuesta que envió pjz también es una buena frase. No tiene programas adicionales, procesos y puede ejecutarse un poco más rápido.
user14645

3
sed X cmdlee cmdy no lo ejecuta. O cmd | sed 's/^/[ERROR] /'o sed 's/^/[ERROR] /' <(cmd)o cmd > >(sed 's/^/[ERROR] /'). Pero cuidado con lo último. Incluso eso le permite acceder al valor de retorno de cmdlas sedejecuciones en segundo plano, por lo que es probable que vea la salida después de que finalice el cmd. Sin embargo, es bueno para iniciar sesión en un archivo. Y tenga en cuenta que awkprobablemente es más rápido que sed.
Tino

Agradable. Este comando tiene un alias fácil. alias lpad="sed 's/^/ /'". en lugar de ERROR inserto 4 espacios iniciales. Ahora, para el truco de magia: ls | lpad | pbcopyantepondrá la salida ls con 4 espacios que lo marcan como Markdown para el código , lo que significa que pega el portapapeles ( pbcopy lo agarra, en Mac) directamente en StackOverflow o cualquier otro contexto de reducción. No se pudo responder aliasel awk (en el primer intento), así que este gana. La lectura, mientras que la solución es también capaz de alias, pero me parece que esta sed más expresiva.
JL Peyret

8

Creé un repositorio de GitHub para hacer algunas pruebas de velocidad.

El resultado es:

  • En el caso general, awkes el más rápido. sedes un poco más lento y perlno es mucho más lento que sed. Aparentemente, todos esos son lenguajes altamente optimizados para el procesamiento de texto.
  • En situaciones muy especiales, donde dominan los tenedores, ejecutar su script como un kshscript compilado ( shcomp) puede ahorrar aún más tiempo de procesamiento. Por el contrario, bashes muy lento en comparación con los kshscripts compilados .
  • La creación de un binario estáticamente vinculado para vencer awkno parece valer la pena.

Por el contrario, pythones muy lento, pero no he probado un caso compilado, porque generalmente no es lo que haría en un caso de secuencias de comandos.

Se prueban las siguientes variantes:

while read line; do echo "[TEST] $line"; done
while read -r line; do echo "[TEST] $line"; done
while read -r line; do echo "[TEST]" $line; done
while read -r line; do echo "[TEST]" "$line"; done
sed 's/^/[TEST] /'
awk '{ print "[TEST] " $0 }'
awk -vT="[TEST] " '{ print T $0 }'
awk -vT="[TEST]" '{ print T " " $0 }'
awk -vT="[TEST]" 'BEGIN { T=T " "; } { print T $0 }'
T="[TEST] " awk '{ print ENVIRON["T"] $0 }'
T="[TEST]" awk '{ print ENVIRON["T"] " " $0 }'
T="[TEST]" awk 'BEGIN { T=ENVIRON["T"] " " } { print T $0 }'
perl -ne 'print "[TEST] $_"'

Dos variantes binarias de una de mis herramientas (aunque no está optimizada para la velocidad):

./unbuffered.dynamic -cp'[TEST] ' -q ''
./unbuffered.static -cp'[TEST] ' -q ''

Python tamponado:

python -uSc 'import sys
for line in sys.stdin: print "[TEST]",line,'

Y Python sin búfer:

python -uSc 'import sys
while 1:
 line = sys.stdin.readline()
 if not line: break
 print "[TEST]",line,'

awk -v T="[TEST %Y%m%d-%H%M%S] " '{ print strftime(T) $0 }'emitir una marca de tiempo
Tino


3

Quería una solución que manejara stdout y stderr, así que escribí prepend.shy la puse en mi camino:

#!/bin/bash

prepend_lines(){
  local prepended=$1
  while read line; do
    echo "$prepended" "$line"
  done
}

tag=$1

shift

"$@" > >(prepend_lines "$tag") 2> >(prepend_lines "$tag" 1>&2)

Ahora solo puedo ejecutar prepend.sh "[ERROR]" cmd ..., para anteponer "[ERROR]" a la salida de cmd, y todavía tengo stderr y stdout separados.


Intenté este enfoque, pero sucedía algo con esas >(subcapas que no pude resolver. Parecía que el script se estaba completando, y la salida llegaba a la terminal después de que el aviso había regresado, lo cual era un poco desordenado. Eventualmente se me ocurrió la respuesta aquí stackoverflow.com/a/25948606/409638
robert
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.