Pregunta

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 que ellos, al principio; y si hay No hay líneas no vacías después de ellas, al final).

¿Es esto posible fuera de un lenguaje de secuencias de comandos totalmente realizado como Perl o Ruby? Preferiría hacer esto con sed o awk si es posible. Básicamente, cualquier herramienta UNIX-Y ampliamente disponible y ampliamente disponible estaría bien, especialmente una de la que puedo aprender más rápidamente (perl, por lo tanto, no incluida).

¿Fue útil?

Solución

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 líderes y siguientes de un archivo, puede combinar los comandos anteriores en:

sed -e :a -e '/./,$!d;/^\n*$/{$d;N;};/\n$/ba' file

Otros consejos

Así que voy a pedir prestada parte de la respuesta de @dogbane para esto, ya que eso sed La línea para eliminar las líneas en blanco principales es tan corta ...

tac es parte de coreutils, y 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 tú necesitar Eficiencia, lo encuentro más legible que todo lo demás hasta ahora.

Aquí hay una solución de un paso en AWK: no comienza a imprimir hasta que ve una línea no vacía y cuando ve una línea vacía, lo recuerda hasta la próxima 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

Nota, 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 para volverse verdaderamente vacías.

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

Como se mencionó en otra respuesta, tac es parte de coreutils, y invierte un archivo. Combinando la idea de hacerlo dos veces con el hecho de que la sustitución de comandos despojará las nuevas líneas, obtenemos

echo "$(echo "$(tac "$filename")" | tac)"

que no depende de sed. Puedes usar echo -n Para quitar la nueva línea de arrastre restante.

Aquí hay una versión SED adaptada, que también considera "vacía" esas 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 (considerando el comentario de Bryanh), pero el punto . en el primer comando se cambió a [^[:blank:]] (cualquier cosa que no sea en blanco) y el \n Dentro de la segunda dirección de comando se cambió a [[:space:]] Para permitir nuevas líneas, espacia una pestaña.

Una versión alternativa, sin usar las clases POSIX, pero su SED debe admitir la inserción \t y \n en el interior […]. 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$

Usando bash

$ filecontent=$(<file)
$ echo "${filecontent/$'\n'}"

En Bash, usando CAT, WC, GREP, SED, Tail and Head:

# 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!

Para una versión eficiente no recursiva de la franja de líneas nuevas (incluidos los personajes "blancos") he desarrollado esto sed guion.

sed -n '/^[[:space:]]*$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^[[:space:]]*$/H'

Utiliza el búfer de retención para almacenar todas las líneas en blanco y las imprime solo después de encontrar una línea no blancas. Si alguien quiere solo las nuevas líneas, es suficiente para deshacerse de los dos [[:space:]]* partes:

sed -n '/^$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^$/H'

He probado una comparación de rendimiento simple con el conocido guión recursivo

sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba'

en un archivo de 3 MB con 1 MB de líneas en blanco aleatorias alrededor de un texto aleatorio Base64.

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 terminó después de 15 minutos. Victoria :)

Por la integridad de la respuesta, las líneas principales que despojan el script SED ya se están transmitiendo bien. Usa el más adecuado para ti.

sed '/[^[:blank:]]/,$!d'
sed '/./,$!d'

A bash solución.

Nota: Solo útil Si el archivo es lo suficientemente pequeño ser leído en la memoria a la vez.

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
  • $(<file) lee todo el archivo y las transmisiones arrastre nuevas líneas, porque la sustitución del comando ($(....)) implícitamente hace eso.
  • =~ es el bash operador de correspondencia de expresión regular, y =~ ^$'\n'*(.*)$ opcionalmente coincide con cualquier principal nuevas líneas (con avidez), y captura lo que viene después. Tenga en cuenta el potencialmente confuso $'\n', que inserta una nueva línea literal usando Ansi c citando, porque la secuencia de escape \n no es apoyado.
  • Tenga en cuenta que esta regex en particular siempre coincidencias, entonces el comando después && es siempre ejecutado.
  • Variable de matriz especial BASH_REMATCH La revancha contiene los resultados de la coincidencia de regex más reciente y el elemento de matriz [1] Contiene lo que se capturó la (primera y única) subexpresión paréntesis (Grupo de Captura), que es la cadena de entrada con cualquier línea nueva líder despojada. El efecto neto es que ${BASH_REMATCH[1]} Contiene el contenido del archivo de entrada con líneas nuevas y seguidas despojadas.
  • Tenga en cuenta que la impresión con echo agrega una sola nueva línea. Si quieres evitar eso, usa echo -n en su lugar (o use el más portátil printf '%s').

Me gustaría introducir 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

@Dogbane tiene una respuesta simple y agradable para eliminar las líneas vacías líderes. Aquí hay un simple comando AWK que elimina solo las líneas finales. Use esto con el comando SED de @Dogbane para eliminar los espacios en blanco líderes y traseros.

awk '{ LINES=LINES $0 "\n"; } /./ { printf "%s", LINES; LINES=""; }'

Esto es bastante simple en funcionamiento.

  • Agregue cada línea a un búfer como lo leemos.
  • Para cada línea que contiene un carácter, imprima el contenido del búfer y luego límpielo.

Entonces, las únicas cosas que se amortiguan y nunca se muestran son en blanco.

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.

Este guión AWK hará el truco:

BEGIN {
    ne=0;
}

/^[[:space:]]*$/ {
    ne++;
}

/[^[:space:]]+/ {
    for(i=0; i < ne; i++)
        print "";
    ne=0;
    print
}

La idea es simple: las líneas vacías no se hacen eco de inmediato. En cambio, esperamos hasta que obtengamos una línea no vacía, y solo entonces primero hacemos eco de tantas líneas vacías como se ve antes, y solo entonces hacemos eco de la nueva línea no vacía.

perl -0pe 's/^\n+|\n+(\n)$/\1/gs'
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top