Rimuovere le righe di intestazione extra dal file, ad eccezione della prima riga


18

Ho un file che assomiglia a questo esempio di giocattolo. Il mio file attuale ha 4 milioni di righe, di cui circa 10 da eliminare.

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

Voglio eliminare le righe che assomigliano all'intestazione, ad eccezione della prima riga.

File finale:

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

Come posso fare questo?

Risposte:


26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. prendere la riga di intestazione dal file di input in una variabile
  2. stampa l'intestazione
  3. elabora il file grepper omettere le righe che corrispondono all'intestazione
  4. acquisire l'output dai due passaggi precedenti nel file di output

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

Entrambe buone aggiunte. Grazie a don_crissti per aver indirettamente sottolineato che posix ha rimosso di recente la sintassi -1 dalla testa, a favore di -n 1.
Jeff Schaller

3
@JeffSchaller, recentemente come in 12 anni fa. Ed head -1è stato obsoleto per decenni prima.
Stéphane Chazelas,

36

Puoi usare

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

Ciò eliminerà le righe con ID a partire dalla riga 2.


3
simpatico; o per essere più specifici con la corrispondenza del modello, sed '2,${/^ID Data1 Data2$/d;}' file(usando il giusto numero di spazi tra le colonne, ovviamente)
Jeff Schaller

Hm, ho pensato che potresti omettere il punto e virgola per un solo comando, ma ok.
bkmoney,

Non w / sane seds, no.
Mikeserv,

aaaand -i per la vittoria di modifica sul posto.
user2066657,

4
Oppuresed '1!{/ID/d;}'
Stéphane Chazelas il

10

Per chi non ama le parentesi graffe

sed -e '1n' -e '/^ID/d'
  • nsignifica passlinea n.1
  • d elimina tutte le righe corrispondenti che iniziano con ^ID

5
Questo può anche essere abbreviato in sed '1n;/^ID/d'nome file. solo un suggerimento
Valentin Bajrami il

Nota che questo stamperà anche linee come quelle IDfooche non sono le stesse dell'intestazione (è improbabile che in questo caso faccia la differenza, ma non lo sai mai).
terdon

6

Eccone uno divertente. Puoi usare seddirettamente per eliminare tutte le copie della prima riga e lasciare tutto il resto al suo posto (inclusa la prima riga stessa).

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

1{h;n;}mette la prima riga nello spazio di attesa, la stampa e legge nella riga successiva, saltando il resto dei sedcomandi per la prima riga. ( Salta1 anche quel primo test per la seconda riga , ma non importa perché quel test non si sarebbe applicato alla seconda riga.)

G aggiunge una nuova riga seguita dal contenuto dello spazio di attesa allo spazio modello.

/^\(.*\)\n\1$/delimina il contenuto dello spazio del modello (saltando così alla riga successiva) se la porzione successiva alla nuova riga (ovvero ciò che è stato aggiunto dallo spazio di attesa) corrisponde esattamente alla porzione prima della nuova riga. Qui vengono eliminate le righe che duplicano l'intestazione.

s/\n.*$//elimina la parte di testo che è stata aggiunta dal Gcomando, in modo che ciò che viene stampato sia solo la riga di testo dal file.

Tuttavia, poiché regex è costoso, un approccio leggermente più veloce sarebbe quello di utilizzare la stessa condizione (negata) e Print fino alla nuova riga se la porzione successiva alla nuova riga (ovvero ciò che è stato aggiunto dallo spazio di attesa) non corrisponde esattamente alla porzione prima della nuova riga e quindi eliminare incondizionatamente lo spazio del modello:

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

L'output quando viene fornito l'input è:

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


@don_crissti, aggiunta interessante; Grazie! Probabilmente opterei per il più lungo ma equivalente sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; in qualche modo è più facile per me leggere. :)
Wildcard


5

Ecco un altro paio di opzioni che non richiedono di conoscere la prima riga in anticipo:

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

Il -nflag dice a perl di scorrere sopra il suo file di input, salvando ogni riga come $_. Il $k=$_ if $.==1;salva la prima linea ( $.è il numero di riga, quindi $.==1sarà solo vero per la linea 1 °) come $k. Le print unless $k eq $_stampe della linea corrente, se non è lo stesso di quello salvato in $k.

In alternativa, la stessa cosa in awk:

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

Qui, testiamo se la riga corrente è la stessa di quella salvata nella variabile x. Se il test viene $0!=xvalutato su true (se la riga corrente $0non è la stessa di x), la riga verrà stampata poiché verrà stampata l'azione predefinita per awk sulle espressioni true. La prima riga ( NR==1) viene salvata come x. Poiché ciò viene eseguito dopo aver verificato se la riga corrente corrisponde x, ciò garantisce che anche la prima riga venga stampata.


Mi piace non dover conoscere l'idea di prima linea poiché la rende uno script generalizzato per la tua cassetta degli attrezzi.
Mark Stewart,

1
quel metodo awk crea una voce array vuota / falsa per riga distinta; per le linee 4M se tutte diverse (non chiare da Q) e abbastanza corte (appare così) questo probabilmente va bene, ma se ci sono molte più o più linee questo potrebbe spezzarsi o morire. !($0 in a)prova senza creare ed evita questo, o awk può fare la stessa logica che hai per perl: '$0!=x; NR==1{x=$0}'o se la riga di intestazione può essere vuota'NR==1{x=$0;print} $0!=x'
dave_thompson_085

1
@ dave_thompson_085 dove viene creata una matrice per riga? Intendi !a[$0]? Perché ciò creerebbe una voce a?
terdon

1
Perché è così che funziona Awk; consultare gnu.org/software/gawk/manual/html_node/… in particolare la "NOTA".
dave_thompson_085,

1
@ dave_thompson_085 beh, sarò dannato! Grazie, non ne ero a conoscenza. Riparato ora.
terdon

4

AWK è uno strumento abbastanza decente anche per questo scopo. Ecco un esempio di esecuzione del codice:

$ 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

Ripartizione :

  • NR == 1 {print} ci dice di stampare la prima riga del file di testo
  • NR != 1 && $0!~/ID Data1 Data2/ l'operatore logico &&dice ad AWK di stampare una linea che non è uguale a 1 e che non contiene ID Data1 Data2. Nota la mancanza di {print}parte; in awk se una condizione di test viene valutata su true, si presume che la riga venga stampata.
  • | head -n 10è solo una piccola aggiunta per limitare l'output a solo le prime 10 righe. Non pertinente alla AWKparte stessa, utilizzato solo a scopo dimostrativo.

Se lo desideri in un file, reindirizza l'output del comando aggiungendo > newFile.txtalla fine del comando, in questo modo:

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

Come regge? Abbastanza buono in realtà:

$ 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 a margine

Il file di esempio generato è stato eseguito per eseguire il loop da uno a un milione e stampare le prime quattro righe del file (quindi 4 righe per milione equivalgono a 4 milioni di righe), che ha richiesto 0,09 secondi.

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

Nota che questo stamperà anche linee come quelle ID Data1 Data2 fooche non sono le stesse dell'intestazione (è improbabile che in questo caso faccia la differenza, ma non lo sai mai).
terdon

@terdon sì, esattamente. OP ha tuttavia specificato solo un modello che desidera rimuovere e il suo esempio sembra supportarlo
Sergiy Kolodyazhnyy,

3

Awk, adattandosi automaticamente a qualsiasi intestazione:

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

cioè, sulla prima riga, ottieni l'intestazione e stampala, e la riga successiva DIVERSA da quella intestazione viene stampata.

FNR = Numero di record nel file corrente, in modo che tu possa avere più file e farà lo stesso in ciascuno di essi.


2

Per completezza, la soluzione Perl IMO è leggermente più elegante di @terdon:

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

1
Ah, ma il mio punto era evitare la necessità di specificare lo schema e di leggerlo invece dalla prima riga. Il tuo approccio eliminerà semplicemente qualsiasi riga che inizia con ID. Non hai alcuna garanzia che questo non eliminerà le righe che dovrebbero essere mantenute. Dal momento che hai sollevato l'eleganza, gè inutile se usi ^e $. In effetti, tutte le opzioni m///sono inutili qui tranne s; attivano funzionalità che non stai utilizzando. Anche il $, s/^ID.*//sfarebbe la stessa cosa.
terdon

@terdon, abbastanza giusto. Il tuo è molto più universale!
KWubbufetowicz,

2

Solo per respingere un po 'la domanda ... sembra che forse il tuo input sia esso stesso il risultato di catturare insieme diversi file TSV. Se è possibile eseguire il backup di un passaggio nella pipeline di elaborazione (se lo si possiede o si può parlare con le persone che lo fanno), è possibile utilizzare uno strumento sensibile all'intestazione per concatenare i dati in primo luogo e quindi rimuovere il problema di dover rimuove le righe di intestazione aggiuntive.

Ad esempio, 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
Grazie per aver aggiunto questo bocconcino. Ciò sarà estremamente utile in futuro, poiché la maggior parte delle mie pipeline richiede l'unione e l'unione di file da singoli campioni.
Caio Augusto,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.