Eliminar líneas de encabezado adicionales del archivo, excepto la primera línea


18

Tengo un archivo que se parece a este ejemplo de juguete. Mi archivo real tiene 4 millones de líneas, de las cuales necesito eliminar aproximadamente 10.

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
ID  Data1  Data2
4    100    100
ID  Data1  Data2
5    200    200

Quiero eliminar las líneas que se parecen al encabezado, excepto la primera línea.

Archivo final:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200

¿Cómo puedo hacer esto?

Respuestas:


26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. tomar la línea del encabezado del archivo de entrada en una variable
  2. imprime el encabezado
  3. procesar el archivo con greppara omitir líneas que coincidan con el encabezado
  4. capturar la salida de los dos pasos anteriores en el archivo de salida

2
o tal vez{ IFS= read -r head; printf '%s\n' "$head"; grep -vF "$head" ; } <file
iruvar

Ambas buenas adiciones. Gracias a don_crissti por señalar indirectamente que posix eliminó recientemente la sintaxis -1 de la cabeza, a favor de -n 1.
Jeff Schaller

3
@JeffSchaller, recientemente como hace 12 años. Y head -1ha quedado obsoleto durante décadas antes de eso.
Stéphane Chazelas

36

Puedes usar

sed '2,${/ID/d;}'

Esto eliminará las líneas con ID a partir de la línea 2.


3
bonito; o para ser más específico con la coincidencia de patrones sed '2,${/^ID Data1 Data2$/d;}' file(usando el número correcto de espacios entre las columnas, por supuesto)
Jeff Schaller

Hm, pensé que podría omitir el punto y coma por solo 1 comando, pero está bien.
bkmoney

No con sanos sed, no.
mikeserv

aaaand -i para la victoria de edición in situ.
user2066657

44
Osed '1!{/ID/d;}'
Stéphane Chazelas el

10

Para los que no les gustan las llaves

sed -e '1n' -e '/^ID/d'
  • nsignifica passlínea no.1
  • d eliminar todas las líneas coincidentes que comienzan con ^ID

55
Esto también se puede acortar a sed '1n;/^ID/d'nombre de archivo. solo una sugerencia
Valentin Bajrami

Tenga en cuenta que esto también imprimirá líneas como las IDfooque no son las mismas que el encabezado (es poco probable que haga una diferencia en este caso, pero nunca se sabe).
terdon

6

Aquí hay uno divertido. Puede usar seddirectamente para quitar todas las copias de la primera línea y dejar todo lo demás en su lugar (incluida la primera línea).

sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//' input

1{h;n;}coloca la primera línea en el espacio de espera, la imprime y lee en la siguiente línea, omitiendo el resto de los sedcomandos para la primera línea. (También omite esa primera 1prueba para la segunda línea , pero eso no importa ya que esa prueba no se aplicaría a la segunda línea).

G agrega una nueva línea seguida del contenido del espacio de retención al espacio del patrón.

/^\(.*\)\n\1$/delimina el contenido del espacio del patrón (saltando así a la siguiente línea) si la porción después de la nueva línea (es decir, lo que se agregó desde el espacio de retención) coincide exactamente con la porción anterior a la nueva línea. Aquí es donde se eliminarán las líneas que duplican el encabezado.

s/\n.*$//elimina la porción de texto que agregó el Gcomando, de modo que lo que se imprime es solo la línea de texto del archivo.

Sin embargo, dado que la expresión regular es costosa, un enfoque un poco más rápido sería usar la misma condición (negada) y Palinearse con la nueva línea si la porción después de la nueva línea (es decir, lo que se agregó desde el espacio de espera) no coincide exactamente con la porción antes de la nueva línea y luego elimine incondicionalmente el espacio del patrón:

sed '1{h;n;};G;/^\(.*\)\n\1$/!P;d' input

La salida cuando se le da su entrada es:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200


@don_crissti, adición interesante; ¡Gracias! Probablemente optaría por el más largo pero equivalente sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; de alguna manera es más fácil para mí leer. :)
Comodín


5

Aquí hay un par de opciones más que no requieren que conozca la primera línea de antemano:

perl -ne 'print unless $_ eq $k; $k=$_ if $.==1; 

La -nbandera le dice a Perl que recorra su archivo de entrada, guardando cada línea como $_. El $k=$_ if $.==1;guarda la primera línea ( $.es el número de línea, por $.==1lo que solo será cierto para la primera línea) como $k. Las print unless $k eq $_impresiones de la línea actual si no es la misma que la que guarda en $k.

Alternativamente, lo mismo en awk:

awk '$0!=x;(NR==1){x=$0}' file 

Aquí, probamos si la línea actual es la misma que la guardada en la variable x. Si la prueba se $0!=xevalúa como verdadera (si la línea actual $0no es la misma que x), la línea se imprimirá porque la acción predeterminada para awk en expresiones verdaderas es imprimir. La primera línea ( NR==1) se guarda como x. Dado que esto se hace después de verificar si la línea actual coincide x, esto garantiza que la primera línea también se imprima.


Me gusta no tener que conocer la idea de la primera línea, ya que la convierte en un script generalizado para su caja de herramientas.
Mark Stewart el

1
ese método awk crea una entrada de matriz vacía / falsa por línea distinta; para líneas 4M si todas son diferentes (no claras de Q) y bastante cortas (parece), esto probablemente esté bien, pero si hay líneas mucho más largas o más largas, esto podría golpear o morir. !($0 in a)prueba sin crear y evita esto, o awk puede hacer la misma lógica que tiene para perl: '$0!=x; NR==1{x=$0}'o si la línea del encabezado puede estar vacía'NR==1{x=$0;print} $0!=x'
dave_thompson_085

1
@ dave_thompson_085 ¿dónde se crea una matriz por línea? Quieres decir !a[$0]? ¿Por qué crearía eso una entrada a?
terdon

1
Porque así es como funciona awk; vea gnu.org/software/gawk/manual/html_node/… especialmente la "NOTA".
dave_thompson_085

1
@ dave_thompson_085 bueno, ¡estaré condenado! Gracias, no estaba al tanto de eso. Corregido ahora.
terdon

4

AWK es una herramienta bastante decente para tal propósito también. Aquí hay una muestra de código:

$ awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt | head -n 10                                
ID  Data1  Data2
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100

Descomponer :

  • NR == 1 {print} nos dice que imprimamos la primera línea del archivo de texto
  • NR != 1 && $0!~/ID Data1 Data2/ El operador lógico &&le dice a AWK que imprima una línea que no es igual a 1 y que no contiene ID Data1 Data2. Tenga en cuenta la falta de {print}parte; en awk si una condición de prueba se evalúa como verdadera, se supone que la línea se imprimirá.
  • | head -n 10es solo una pequeña adición para limitar la salida a solo las primeras 10 líneas. No es relevante para la AWKparte en sí, solo se utiliza con fines de demostración.

Si desea eso en un archivo, redirija la salida del comando agregando > newFile.txtal final del comando, así:

awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > newFile.txt

¿Cómo se sostiene? Bastante bueno en realidad:

$ time awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > /dev/null                            
    0m3.60s real     0m3.53s user     0m0.06s system

Nota al margen

El archivo de muestra generado se realizó con un bucle de uno a un millón e imprimió las primeras cuatro líneas de su archivo (por lo tanto, 4 líneas por millón equivalen a 4 millones de líneas), que por cierto tardó 0.09 segundos.

awk 'BEGIN{ for(i=1;i<=1000000;i++) printf("ID  Data1  Data2\n1    100    100\n     100    200\n3    200    100\n");  }' > rmLines.txt

Tenga en cuenta que esto también imprimirá líneas como las ID Data1 Data2 fooque no son las mismas que el encabezado (es poco probable que haga una diferencia en este caso, pero nunca se sabe).
terdon

@terdon sí, exactamente correcto. OP, sin embargo, especificó solo un patrón que quieren eliminar y su ejemplo parece apoyar eso
Sergiy Kolodyazhnyy

3

Awk, adaptándose a cualquier encabezado automáticamente:

awk '( FNR == 1) {header=$0;print $0;}
     ( FNR > 1) && ($0 != header) { print $0;}'  file1  file2 ....

es decir, en la primera línea, obtenga el encabezado e imprímalo, y se imprimirá la línea posterior DIFERENTE de ese encabezado.

FNR = Número de registros en el archivo actual, para que pueda tener varios archivos y haga lo mismo en cada uno de ellos.


2

En aras de la exhaustividad, la solución Perl IMO es un poco más elegante que @terdon:

perl -i -p -e 's/^ID.*$//s if $. > 1' file

1
Ah, pero mi objetivo principal era evitar la necesidad de especificar el patrón y, en su lugar, leerlo desde la primera línea. Su enfoque simplemente eliminará cualquier línea que comience con ID. No tiene garantía de que esto no elimine las líneas que deben mantenerse. Desde que mencionaste la elegancia, no gtiene sentido si lo usas ^y $. De hecho, todas sus opciones m///son inútiles aquí excepto s; activan funciones que no estás usando. Entonces, el $, s/^ID.*//sharía lo mismo.
terdon

@terdon, bastante justo. ¡El tuyo es mucho más universal!
KWubbufetowicz

2

Solo para retroceder un poco en la pregunta ... parece que tal vez su entrada es en sí el resultado de atrapar varios archivos TSV juntos. Si puede hacer una copia de seguridad de un paso en su proceso de procesamiento (si lo posee o puede hablar con las personas que lo hacen), podría usar una herramienta de reconocimiento de encabezado para concatenar los datos en primer lugar, y así eliminar el problema de tener que eliminar líneas de encabezado adicionales.

Por ejemplo, usando Miller :

$ cat f1.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
$ cat f2.tsv
ID  Data1 Data2
4 100 100
$ cat f3.tsv
ID  Data1 Data2
5 200 200

$ cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
ID  Data1 Data2
4 100 100
ID  Data1 Data2
5 200 200

$ mlr --tsvlite cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
4 100 100
5 200 200

1
Gracias por agregar este dato. Esto será extremadamente útil en el futuro, ya que la mayoría de mis canalizaciones requieren unir y fusionar archivos de muestras individuales.
Cayo Augusto el
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.