¿Cómo puedo eliminar una nueva línea final en bash?
10
Estoy buscando algo que se comporte como el de Perl chomp. Estoy buscando un comando que simplemente imprima su entrada, menos el último carácter si es una nueva línea:
$ printf "one\ntwo\n"| COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo"| COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
(La sustitución de comandos en Bash y Zsh elimina todas las líneas nuevas finales, pero estoy buscando algo que elimine una línea nueva como máximo).
Si desea un equivalente exacto para chomp, el primer método que se me ocurre es la solución awk que LatinSuD ya publicó . Agregaré algunos otros métodos que no implementan chomppero implementan algunas tareas comunes que a chompmenudo se usan.
Cuando inserta algo de texto en una variable, se eliminan todas las líneas nuevas al final. Entonces, todos estos comandos producen la misma salida de una línea:
Si desea agregar algún texto a la última línea de un archivo o de la salida de un comando, sedpuede ser conveniente. Con GNU sed y la mayoría de las otras implementaciones modernas, esto funciona incluso si la entrada no termina en una nueva línea¹; sin embargo, esto no agregará una nueva línea si aún no había una.
sed '$ s/$/ done/'
¹ Sin embargo, esto no funciona con todas las implementaciones de sed: sed es una herramienta de procesamiento de texto, y un archivo que no está vacío y no termina con un carácter de nueva línea no es un archivo de texto.
Esto no es exactamente equivalente a chomp, ya que chompsolo elimina a lo sumo una nueva línea final.
Flimm
@Flimm Sí, el equivalente exacto más obvio chompsería la solución awk que LatinSuD ya ha publicado. Pero en muchos casos chompes solo una herramienta para hacer un trabajo, y proporciono formas de hacer algunas tareas comunes. Permítanme actualizar mi respuesta para aclarar esto.
Gilles 'SO- deja de ser malvado'
1
Otro perlenfoque Este lee toda la entrada en la memoria, por lo que podría no ser una buena idea para grandes cantidades de datos (use cuonglm's o el awkenfoque para eso):
$ printf "one\ntwo\n"| perl -0777pe's/\n$//'; echo " done"
one
two done
Esa es una solución rápida, ya que necesita leer solo un carácter del archivo y luego eliminarlo directamente ( truncate) sin leer todo el archivo.
Sin embargo, al trabajar con datos de stdin (una secuencia), los datos deben leerse, todo. Y, se "consume" tan pronto como se lee. Sin retroceso (como con truncar). Para encontrar el final de una secuencia necesitamos leer hasta el final de la secuencia. En ese punto, no hay forma de retroceder en la secuencia de entrada, los datos ya se han "consumido". Esto significa que los datos deben almacenarse en alguna forma de búfer hasta que coincidamos con el final de la secuencia y luego hacer algo con los datos en el búfer.
La solución más obvia es convertir la secuencia en un archivo y procesar ese archivo. Pero la pregunta pide algún tipo de filtro de la corriente. No se trata del uso de archivos adicionales.
variable
La solución ingenua sería capturar toda la entrada en una variable:
FilterOne(){ filecontents=$(cat; echo "x");# capture the whole input
filecontents=${filecontents%x};# Remove the "x" added above.
nl=$'\n';# use a variable for newline.
printf '%s'"${filecontents%"$nl"}";# Remove newline (if it exists).}
printf 'one\ntwo'|FilterOne; echo 1done
printf 'one\ntwo\n'|FilterOne; echo 2done
printf 'one\ntwo\n\n'|FilterOne; echo 3done
memoria
Es posible cargar un archivo completo en la memoria con sed. En sed es imposible evitar la nueva línea final en la última línea. GNU sed podría evitar imprimir una nueva línea final, pero solo si el archivo fuente ya no se encuentra. Entonces, no, sed simple no puede ayudar.
Excepto en GNU awk con la -zopción:
sed -z 's/\(.*\)\n$/\1/'
Con awk (cualquier awk), sorbe toda la secuencia y printfsin la nueva línea final.
Cargar un archivo completo en la memoria puede no ser una buena idea, puede consumir mucha memoria.
Dos líneas en la memoria
En awk, podemos procesar dos líneas por ciclo almacenando la línea anterior en una variable e imprimiendo la actual:
awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'
Procesamiento directo
Pero podríamos hacerlo mejor.
Si imprimimos la línea actual sin una nueva línea e imprimimos una nueva línea solo cuando existe una línea siguiente, procesamos una línea a la vez y la última línea no tendrá una nueva línea final:
chomp
, ya quechomp
solo elimina a lo sumo una nueva línea final.chomp
sería la solución awk que LatinSuD ya ha publicado. Pero en muchos casoschomp
es solo una herramienta para hacer un trabajo, y proporciono formas de hacer algunas tareas comunes. Permítanme actualizar mi respuesta para aclarar esto.Otro
perl
enfoque Este lee toda la entrada en la memoria, por lo que podría no ser una buena idea para grandes cantidades de datos (use cuonglm's o elawk
enfoque para eso):fuente
Enganché esto de un repositorio de Github en alguna parte, pero no puedo encontrar dónde
delete-trailing-blank-lines-sed
fuente
resumen
Imprima líneas sin nueva línea, agregue una nueva línea solo si hay otra línea para imprimir.
Otras soluciones
Si estuviéramos trabajando con un archivo, podemos truncar un carácter de él (si termina en una nueva línea):
removeTrailNewline () {[[$ (tail -c 1 "$ 1")]] || truncar -s-1 "$ 1"; }
Esa es una solución rápida, ya que necesita leer solo un carácter del archivo y luego eliminarlo directamente (
truncate
) sin leer todo el archivo.Sin embargo, al trabajar con datos de stdin (una secuencia), los datos deben leerse, todo. Y, se "consume" tan pronto como se lee. Sin retroceso (como con truncar). Para encontrar el final de una secuencia necesitamos leer hasta el final de la secuencia. En ese punto, no hay forma de retroceder en la secuencia de entrada, los datos ya se han "consumido". Esto significa que los datos deben almacenarse en alguna forma de búfer hasta que coincidamos con el final de la secuencia y luego hacer algo con los datos en el búfer.
La solución más obvia es convertir la secuencia en un archivo y procesar ese archivo. Pero la pregunta pide algún tipo de filtro de la corriente. No se trata del uso de archivos adicionales.
variable
La solución ingenua sería capturar toda la entrada en una variable:
memoria
Es posible cargar un archivo completo en la memoria con sed. En sed es imposible evitar la nueva línea final en la última línea. GNU sed podría evitar imprimir una nueva línea final, pero solo si el archivo fuente ya no se encuentra. Entonces, no, sed simple no puede ayudar.
Excepto en GNU awk con la
-z
opción:Con awk (cualquier awk), sorbe toda la secuencia y
printf
sin la nueva línea final.Cargar un archivo completo en la memoria puede no ser una buena idea, puede consumir mucha memoria.
Dos líneas en la memoria
En awk, podemos procesar dos líneas por ciclo almacenando la línea anterior en una variable e imprimiendo la actual:
Procesamiento directo
Pero podríamos hacerlo mejor.
Si imprimimos la línea actual sin una nueva línea e imprimimos una nueva línea solo cuando existe una línea siguiente, procesamos una línea a la vez y la última línea no tendrá una nueva línea final:
awk 'NR == 1 {printf ("% s", $ 0); siguiente}; {printf ("\ n% s", $ 0)} '
O, escrito de alguna otra manera:
O:
Entonces:
fuente