unix - texto - Eliminando las nuevas líneas de inicio/final con sed, awk, tr y amigos
sed reemplazar/ (12)
Me gustaría eliminar todas las líneas vacías de un archivo, pero solo cuando están al final / inicio de un archivo (es decir, si no hay líneas no vacías antes de ellas, al comienzo, y si hay no hay líneas no vacías después de ellos, al final).
¿Es esto posible fuera de un lenguaje de scripts completo como Perl o Ruby? Prefiero hacer esto con sed
o awk
si es posible. Básicamente, cualquier herramienta UNIX-y ligera y ampliamente disponible estaría bien, especialmente una sobre la que puedo aprender más rápidamente (Perl, por lo tanto, no incluida).
@dogbane tiene una buena respuesta simple para eliminar las líneas vacías principales. Aquí hay un comando awk simple que elimina solo las líneas finales. Use esto con el comando sed de @ dogbane para eliminar los espacios en blanco iniciales y finales.
awk ''{ LINES=LINES $0 "/n"; } /./ { printf "%s", LINES; LINES=""; }''
Esto es bastante simple en operación.
- Agregue cada línea a un buffer mientras lo leemos.
- Para cada línea que contiene un carácter, imprima el contenido del búfer y luego desactívela.
Entonces, lo único que se almacena en el búfer y nunca se muestra son los espacios en blanco finales.
Utilicé printf en lugar de impresión para evitar la adición automática de una nueva línea, ya que estoy usando nuevas líneas para separar las líneas en el búfer.
Aquí hay una solución de una pasada en awk: no comienza a imprimir hasta que ve una línea no vacía y cuando ve una línea vacía, la recuerda hasta la siguiente línea no vacía
awk ''
/[[:graph:]]/ {
# a non-empty line
# set the flag to begin printing lines
p=1
# print the accumulated "interior" empty lines
for (i=1; i<=n; i++) print ""
n=0
# then print this line
print
}
p && /^[[:space:]]*$/ {
# a potentially "interior" empty line. remember it.
n++
}
'' filename
Tenga en cuenta que debido al mecanismo que estoy usando para considerar líneas vacías / no vacías (con [[:graph:]]
y /^[[:space:]]*$/
), las líneas interiores con solo espacios en blanco se truncarán estar verdaderamente vacío
Aquí hay una versión sed adaptada, que también considera "vacía" aquellas líneas con solo espacios y pestañas.
sed -e :a -e ''/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba'' -e ''}''
Básicamente es la versión de respuesta aceptada (teniendo en cuenta el comentario de Bryan), pero el punto .
en el primer comando se cambió a [^[:blank:]]
(cualquier cosa que no esté en blanco) y /n
dentro de la segunda dirección de comando se cambió a [[:space:]]
para permitir nuevas líneas, espacios y pestañas.
Una versión alternativa, sin utilizar las clases POSIX, pero su sed debe ser compatible con la inserción de /t
y /n
dentro de […]
. GNU sed hace, BSD sed no.
sed -e :a -e ''/[^/t ]/,$!d; /^[/n/t ]*$/{ $d; N; ba'' -e ''}''
Pruebas:
prompt$ printf ''/n /t /n/nfoo/n/nfoo/n/n /t /n/n''
foo
foo
prompt$ printf ''/n /t /n/nfoo/n/nfoo/n/n /t /n/n'' | sed -n l
$
/t $
$
foo$
$
foo$
$
/t $
$
prompt$ printf ''/n /t /n/nfoo/n/nfoo/n/n /t /n/n'' | sed -e :a -e ''/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba'' -e ''}''
foo
foo
prompt$
Así que voy a pedir prestada parte de la respuesta de @ dogbane para esto, ya que esa línea sed
para eliminar las líneas en blanco delanteras es tan corta ...
tac
es parte de coreutils , e invierte un archivo. Así que hazlo dos veces:
tac file | sed -e ''/./,$!d'' | tac | sed -e ''/./,$!d''
Ciertamente no es el más eficiente, pero a menos que necesite eficiencia, hasta ahora lo veo más legible que todo lo demás.
Como se menciona en share , tac
es parte de coreutils y revierte un archivo. Combinando la idea de hacerlo dos veces con el hecho de que la sustitución de comandos despojará a las nuevas líneas , obtenemos
echo "$(echo "$(tac "$filename")" | tac)"
que no depende de sed
Puede usar echo -n
para quitar la nueva línea final restante.
De scripts útiles de una línea para sed :
# Delete all leading blank lines at top of file (only).
sed ''/./,$!d'' file
# Delete all trailing blank lines at end of file (only).
sed -e :a -e ''/^/n*$/{$d;N;};//n$/ba'' file
Por lo tanto, para eliminar las líneas en blanco iniciales y finales de un archivo, puede combinar los comandos anteriores en:
sed -e :a -e ''/./,$!d;/^/n*$/{$d;N;};//n$/ba'' file
En bash, usando cat, wc, grep, sed, cola y cabeza:
# number of first line that contains non-empty character
i=`grep -n "^[^/B*]" <your_file> | sed -e ''s/:.*//'' | head -1`
# number of hte last one
j=`grep -n "^[^/B*]" <your_file> | sed -e ''s/:.*//'' | tail -1`
# overall number of lines:
k=`cat <your_file> | wc -l`
# how much empty lines at the end of file we have?
m=$(($k-$j))
# let strip last m lines!
cat <your_file> | head -n-$m
# now we have to strip first i lines and we are done 8-)
cat <your_file> | tail -n+$i
¡Hombre, definitivamente vale la pena aprender un lenguaje de programación "real" para evitar esa fealdad!
Me gustaría presentar otra variante para gawk v4.1 +
result=($(gawk ''
BEGIN {
lines_count = 0;
empty_lines_in_head = 0;
empty_lines_in_tail = 0;
}
/[^[:space:]]/ {
found_not_empty_line = 1;
empty_lines_in_tail = 0;
}
/^[[:space:]]*?$/ {
if ( found_not_empty_line ) {
empty_lines_in_tail ++;
} else {
empty_lines_in_head ++;
}
}
{
lines_count ++;
}
END {
print (empty_lines_in_head " " empty_lines_in_tail " " lines_count);
}
'' "$file"))
empty_lines_in_head=${result[0]}
empty_lines_in_tail=${result[1]}
lines_count=${result[2]}
if [ $empty_lines_in_head -gt 0 ] || [ $empty_lines_in_tail -gt 0 ]; then
echo "Removing whitespace from /"$file/""
eval "gawk -i inplace ''
{
if ( NR > $empty_lines_in_head && NR <= $(($lines_count - $empty_lines_in_tail)) ) {
print
}
}
'' /"$file/""
fi
Para una versión eficiente no recursiva de la tira de nuevas líneas al final (incluidos los caracteres "blancos") he desarrollado este script sed
.
sed -n ''/^[[:space:]]*$/ !{x;//n/{s/^/n//;p;s/.*//;};x;p;}; /^[[:space:]]*$/H''
Utiliza el almacenamiento intermedio de retención para almacenar todas las líneas en blanco y las imprime solo después de que encuentra una línea que no está en blanco. Si alguien quiere solamente las nuevas líneas, es suficiente para deshacerse de las dos partes [[:space:]]*
:
sed -n ''/^$/ !{x;//n/{s/^/n//;p;s/.*//;};x;p;}; /^$/H''
Probé una comparación de rendimiento simple con el conocido script recursivo
sed -e :a -e ''/^/n*$/{$d;N;};//n$/ba''
en un archivo de 3MB con 1MB de líneas en blanco al azar alrededor de un texto base64 aleatorio.
shuf -re 1 2 3 | tr -d "/n" | tr 123 " /t/n" | dd bs=1 count=1M > bigfile
base64 </dev/urandom | dd bs=1 count=1M >> bigfile
shuf -re 1 2 3 | tr -d "/n" | tr 123 " /t/n" | dd bs=1 count=1M >> bigfile
El script de transmisión tardó aproximadamente 0,5 segundos en completarse, el recursivo no finalizó después de 15 minutos. Ganar :)
Para completar la respuesta, las principales líneas stripping sed script ya están en buen funcionamiento. Usa el más adecuado para ti.
sed ''/[^[:blank:]]/,$!d''
sed ''/./,$!d''
Una solución de bash
.
Nota: solo es útil si el archivo es lo suficientemente pequeño para ser leído en la memoria a la vez.
[[ $(<file) =~ ^$''/n''*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
-
$(<file)
lee el archivo completo y recorta las nuevas líneas finales , porque la sustitución de comandos ($(....)
) lo hace implícitamente . -
=~
es el operador de coincidencia de expresiones regulares de bash, y=~ ^$''/n''*(.*)$
coincide opcionalmente con cualquier línea nueva (codiciosamente) y captura lo que venga después. Tenga en cuenta el potencialmente confuso$''/n''
, que inserta una nueva línea literal utilizando el citado ANSI C , porque la secuencia de escape/n
no es compatible. - Tenga en cuenta que esta expresión regular particular siempre coincide, por lo que el comando después de
&&
siempre se ejecuta. - Variable especial de matriz
BASH_REMATCH
revanchaBASH_REMATCH
contiene los resultados de la coincidencia deBASH_REMATCH
más reciente, y el elemento de matriz[1]
contiene lo que capturó la (primera y única) subexpresión entre paréntesis (grupo de captura), que es la cadena de entrada con cualquier nueva línea inicial eliminada. El efecto neto es que${BASH_REMATCH[1]}
contiene el contenido del archivo de entrada con nuevas líneas finales y finales eliminadas. - Tenga en cuenta que la impresión con
echo
agrega una nueva línea final. Si desea evitar eso, useecho -n
lugar (o use la letra más portableprintf ''%s''
).
Usando bash
$ filecontent=$(<file)
$ echo "${filecontent/$''/n''}"
usando awk:
awk ''{a[NR]=$0;if($0 && !s)s=NR;}
END{e=NR;
for(i=NR;i>1;i--)
if(a[i]){ e=i; break; }
for(i=s;i<=e;i++)
print a[i];}'' yourFile