Supprimer les lignes d'en-tête supplémentaires du fichier, à l'exception de la première ligne


18

J'ai un fichier qui ressemble à cet exemple de jouet. Mon fichier actuel contient 4 millions de lignes, dont 10 environ que je dois supprimer.

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

Je veux supprimer les lignes qui ressemblent à l'en-tête, à l'exception de la première ligne.

Dossier final:

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

Comment puis-je faire ceci?

Réponses:


26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. saisir la ligne d'en-tête du fichier d'entrée dans une variable
  2. imprimer l'en-tête
  3. traiter le fichier avec greppour omettre les lignes qui correspondent à l'en-tête
  4. capturer la sortie des deux étapes ci-dessus dans le fichier de sortie

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

Les deux bons ajouts. Merci à don_crissti d'avoir indirectement signalé que posix a récemment supprimé la syntaxe -1 de head, en faveur de -n 1.
Jeff Schaller

3
@JeffSchaller, récemment comme il y a 12 ans. Et head -1a été obsolète pendant des décennies avant cela.
Stéphane Chazelas

36

Vous pouvez utiliser

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

Cela supprimera les lignes dont l'ID commence à la ligne 2.


3
agréable; ou pour être plus précis avec le pattern matching, sed '2,${/^ID Data1 Data2$/d;}' file(en utilisant le bon nombre d'espaces entre les colonnes, bien sûr)
Jeff Schaller

Hm je pensais que vous pouviez omettre le point-virgule pour une seule commande, mais ok.
bkmoney

Pas avec sane sed, non.
mikeserv

aaaand -i pour la victoire de modification sur place.
user2066657

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

10

Pour ceux qui n'aiment pas les accolades

sed -e '1n' -e '/^ID/d'
  • nsignifie le passnuméro de ligne1
  • d supprimer toutes les lignes correspondantes commençant par ^ID

5
Cela peut également être raccourci en sed '1n;/^ID/d'nom de fichier. juste une suggestion
Valentin Bajrami

Notez que cela imprimera également des lignes comme celles IDfooqui ne sont pas identiques à l'en-tête (peu susceptibles de faire une différence dans ce cas, mais vous ne savez jamais).
terdon

6

En voici une amusante. Vous pouvez utiliser seddirectement pour supprimer toutes les copies de la première ligne et laisser tout le reste en place (y compris la première ligne elle-même).

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

1{h;n;}place la première ligne dans l'espace d'attente, l'imprime et lit à la ligne suivante, en ignorant le reste des sedcommandes de la première ligne. (Il ignore1 également ce premier test pour la deuxième ligne , mais cela n'a pas d'importance car ce test n'aurait pas été appliqué à la deuxième ligne.)

G ajoute une nouvelle ligne suivie du contenu de l'espace d'attente à l'espace de motif.

/^\(.*\)\n\1$/dsupprime le contenu de l'espace de motif (sautant ainsi à la ligne suivante) si la partie après la nouvelle ligne (c'est-à-dire ce qui a été ajouté de l'espace d'attente) correspond exactement à la partie avant la nouvelle ligne. C'est là que les lignes qui dupliquent l'en-tête seront supprimées.

s/\n.*$//supprime la partie de texte qui a été ajoutée par la Gcommande, de sorte que ce qui est imprimé n'est que la ligne de texte du fichier.

Cependant, étant donné que l'expression régulière coûte cher, une approche légèrement plus rapide consisterait à utiliser la même condition (niée) et à Pimprimer jusqu'à la nouvelle ligne si la partie après la nouvelle ligne (c'est-à-dire ce qui a été ajouté à partir de l'espace d'attente) ne correspond pas exactement à la partie. avant la nouvelle ligne, puis supprimez inconditionnellement l'espace de motif:

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

La sortie lorsque votre entrée est donnée est:

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


@don_crissti, ajout intéressant; Merci! J'opterais probablement pour le plus long mais équivalent sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; en quelque sorte, il est plus facile pour moi de lire. :)
Wildcard


5

Voici quelques choix supplémentaires qui ne nécessitent pas que vous connaissiez la première ligne à l'avance:

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

L' -nindicateur indique à perl de boucler sur son fichier d'entrée, enregistrant chaque ligne sous $_. Le $k=$_ if $.==1;enregistre la première ligne ( $.est le numéro de ligne, donc $.==1ne sera vrai que pour la 1ère ligne) comme $k. Le print unless $k eq $_imprime la ligne actuelle si elle n'est pas la même que celle enregistrée dans $k.

Alternativement, la même chose dans awk:

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

Ici, nous testons si la ligne actuelle est la même que celle enregistrée dans la variable x. Si le test donne la valeur $0!=xtrue (si la ligne actuelle $0n'est pas la même que x), la ligne sera imprimée car l'action par défaut pour awk sur les expressions vraies est d'imprimer. La première ligne ( NR==1) est enregistrée sous x. Comme cela est fait après avoir vérifié si la ligne actuelle correspond x, cela garantit que la première ligne sera également imprimée.


J'aime ne pas avoir à connaître l'idée de première ligne car elle en fait un script généralisé pour votre boîte à outils.
Mark Stewart

1
cette méthode awk crée une entrée de tableau vide / faux par ligne distincte; pour les lignes 4M si toutes différentes (pas claires de Q) et assez courtes (semble donc) c'est probablement correct, mais s'il y a beaucoup plus ou plus de lignes, cela pourrait se bloquer ou mourir. !($0 in a)teste sans créer et évite cela, ou awk peut faire la même logique que vous avez pour perl: '$0!=x; NR==1{x=$0}'ou si la ligne d'en-tête peut être vide'NR==1{x=$0;print} $0!=x'
dave_thompson_085

1
@ dave_thompson_085 où est créé un tableau par ligne? Tu veux dire !a[$0]? Pourquoi cela créerait-il une entrée a?
terdon

1
Parce que c'est comme ça que fonctionne awk; voir gnu.org/software/gawk/manual/html_node/… en particulier la "NOTE".
dave_thompson_085

1
@ dave_thompson_085 et bien je serai damné! Merci, je n'étais pas au courant de cela. Fixé maintenant.
terdon

4

AWK est également un outil tout à fait convenable à cette fin. Voici un exemple de code:

$ 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

Décomposer :

  • NR == 1 {print} nous dit d'imprimer la première ligne du fichier texte
  • NR != 1 && $0!~/ID Data1 Data2/ L'opérateur logique &&indique à AWK d'imprimer une ligne qui n'est pas égale à 1 et ne contient pas ID Data1 Data2. Notez le manque de {print}pièce; dans awk si une condition de test est évaluée à true, il est supposé que la ligne doit être imprimée.
  • | head -n 10est juste un petit ajout pour limiter la sortie aux 10 premières lignes seulement. Ne concerne pas la AWKpièce elle-même, uniquement utilisée à des fins de démonstration.

Si vous le souhaitez dans un fichier, redirigez la sortie de la commande en ajoutant > newFile.txtà la fin de la commande, comme ceci:

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

Comment ça tient? Assez bien en fait:

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

Note latérale

L'exemple de fichier généré a été réalisé avec une boucle de un à un million et l'impression des quatre premières lignes de votre fichier (donc 4 lignes fois le million équivaut à 4 millions de lignes), ce qui a d'ailleurs pris 0,09 seconde.

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

Notez que cela imprimera également des lignes comme celles ID Data1 Data2 fooqui ne sont pas identiques à l'en-tête (peu susceptibles de faire une différence dans ce cas, mais vous ne savez jamais).
terdon

@terdon oui, exactement à droite. OP a cependant spécifié un seul motif qu'ils veulent supprimer et son exemple semble le confirmer
Sergiy Kolodyazhnyy

3

Awk, l'adaptation à n'importe quel en-tête automatiquement:

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

c'est-à-dire, sur la première ligne, obtenez l'en-tête et imprimez-le, et la ligne suivante DIFFÉRENTE de cet en-tête est imprimée.

FNR = nombre d'enregistrements dans le fichier en cours, afin que vous puissiez avoir plusieurs fichiers et qu'il en fera de même dans chacun d'eux.


2

Par souci d'exhaustivité, la solution Perl IMO légèrement plus élégante que @terdon a donné:

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

1
Ah, mais mon objectif était d'éviter la nécessité de spécifier le modèle et de le lire à la première ligne. Votre approche supprimera simplement toute ligne commençant par ID. Vous n'avez aucune garantie que cela ne supprimera pas les lignes qui devraient être conservées. Puisque vous avez évoqué l'élégance, cela ne gsert à rien si vous utilisez ^et $. En fait, toutes vos options m///sont inutiles ici, sauf s; ils activent des fonctionnalités que vous n'utilisez pas. Donc, le $, s/^ID.*//sferait la même chose.
terdon

@terdon, assez bien. Le vôtre est beaucoup plus universel!
KWubbufetowicz

2

Juste pour repousser un peu la question ... il semble que votre entrée soit elle-même le résultat de la fusion de plusieurs fichiers TSV. Si vous pouvez sauvegarder une étape de votre pipeline de traitement (si vous en êtes le propriétaire ou si vous pouvez en parler avec les personnes qui le font), vous pouvez utiliser un outil sensible aux en-têtes pour concaténer les données en premier lieu, et ainsi éliminer le problème d'avoir à supprimer les lignes d'en-tête supplémentaires.

Par exemple, en utilisant 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
Merci d'avoir ajouté ce petit morceau. Cela sera extrêmement utile à l'avenir, car la plupart de mes pipelines nécessitent la jonction et la fusion de fichiers à partir d'échantillons individuels.
Gaius Augustus
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.