web-dev-qa-db-fra.com

Comment puis-je supprimer les lignes en double dans un fichier sous Unix?

Est-il possible de supprimer les lignes en double dans un fichier sous Unix?

Je peux le faire avec sort -u et uniq, mais je veux utiliser sed ou awk. Est-ce possible?

114
Vijay
awk '!seen[$0]++' file.txt

seen est un tableau associatif auquel Awk transmettra chaque ligne du fichier. Si une ligne ne fait pas partie du tableau, alors seen[$0] sera évalué à faux. Le ! est un opérateur logique NOT et inversera le faux en vrai. Awk imprimera les lignes où l'expression est évaluée à true. Le ++ incrémente seen pour que seen[$0] == 1 après la première recherche d’une ligne, puis seen[$0] == 2, etc.
Awk évalue tout sauf 0 et "" (chaîne vide) à true. Si une ligne en double est placée dans seen, alors !seen[$0] sera évalué à faux et la ligne ne sera pas écrite dans la sortie.

251
Jonas Elfström

De http://sed.sourceforge.net/sed1line.txt : (Merci de ne pas me demander comment cela fonctionne ;-))

 # delete duplicate, consecutive lines from a file (emulates "uniq").
 # First line in a set of duplicate lines is kept, rest are deleted.
 sed '$!N; /^\(.*\)\n\1$/!P; D'

 # delete duplicate, nonconsecutive lines from a file. Beware not to
 # overflow the buffer size of the hold space, or else use GNU sed.
 sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
28
Andre Miller

Perl one-liner similaire à la solution awk de @ jonas:

Perl -ne 'print if ! $x{$_}++' file

Cette variante supprime les espaces finaux avant de comparer:

Perl -lne 's/\s*$//; print if ! $x{$_}++' file

Cette variante édite le fichier sur place:

Perl -i -ne 'print if ! $x{$_}++' file

Cette variante édite le fichier sur place et effectue une sauvegarde file.bak

Perl -i.bak -ne 'print if ! $x{$_}++' file
12
Chris Koknat

Le one-liner que Andre Miller a publié ci-dessus fonctionne à l'exception des versions récentes de sed lorsque le fichier d'entrée se termine par une ligne vide et aucun caractère. Sur mon Mac, le processeur tourne tout simplement.

Boucle infinie si la dernière ligne est vide et n’a aucun caractère :

sed '$!N; /^\(.*\)\n\1$/!P; D'

Ne se bloque pas, mais vous perdez la dernière ligne

sed '$d;N; /^\(.*\)\n\1$/!P; D'

L'explication se trouve à la toute fin du sed FAQ :

Le responsable GNU sed a estimé qu'en dépit des problèmes de portabilité
Cela entraînerait, en changeant la commande N pour imprimer (plutôt que
supprimer) l'espace de modèle était plus cohérent avec les intuitions
sur la manière dont une commande "ajoute la ligne suivante" devrait se comporter.
Un autre fait en faveur du changement est que "{N; commande;}" sera
supprime la dernière ligne si le fichier a un nombre impair de lignes, mais
affiche la dernière ligne si le fichier contient un nombre pair de lignes.

Pour convertir des scripts qui utilisaient le comportement précédent de N (suppression
l’espace du motif une fois atteint l’EOF) en scripts compatibles avec
toutes les versions de sed, modifient un "N" isolé; à "$ d; N;" .

7
Bradley Kreider

ne alternative utilisant Vim (compatible Vi):

Supprimer les doublons et les lignes consécutives d'un fichier:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

Supprimez les lignes en double, non consécutives et non vides d'un fichier:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq

4
Bohr

La première solution provient également de http://sed.sourceforge.net/sed1line.txt

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
1
2
3
4
5

l'idée principale est:

print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.

Explique:

  1. $!N;: Si la ligne en cours n'est PAS la dernière ligne, utilisez la commande N pour lire la ligne suivante dans pattern space.
  2. /^(.*)\n\1$/!P: si le contenu du pattern space actuel est composé de deux duplicate string séparés par \n, ce qui signifie que la ligne suivante est le same avec ligne courante, nous ne pouvons PAS l’imprimer selon notre idée de base; sinon, la ligne en cours étant la dernière apparition de toutes ses lignes consécutives dupliquées, nous pouvons maintenant utiliser la commande P pour imprimer les caractères dans le fichier pattern space courant \n (\n Également imprimé).
  3. D: nous utilisons la commande D pour supprimer les caractères du pattern space Util actuel \n (\n Également supprimé), puis le contenu de pattern space Est la ligne suivante.
  4. et la commande D force sed à passer à la commande FIRST$!N, mais ne lit PAS la ligne suivante du fichier ou du flux d'entrée standard.

La deuxième solution est facile à comprendre (de moi-même):

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
1
2
3
4
5

l'idée principale est:

print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.

Explique:

  1. lit une nouvelle ligne à partir du flux d'entrée ou du fichier et l'imprime une fois.
  2. la commande use :loop définit un label nommé loop.
  3. utilisez N pour lire la ligne suivante dans le pattern space.
  4. utilisez s/^(.*)\n\1$/\1/ pour supprimer la ligne en cours si la ligne suivante est identique à la ligne en cours, nous utilisons la commande s pour effectuer l'action delete.
  5. si la commande s est exécutée avec succès, utilisez la commande tloop pour forcer sed à accéder au label nommé loop, ce qui Dans la même boucle que les lignes suivantes, il n'y a pas de lignes consécutives en double de la ligne qui est latest printed; sinon, utilisez la commande D pour delete la même ligne que le latest-printed line et forcez sed pour passer à la première commande, qui est la p, le contenu de l'actuel pattern space Est la nouvelle ligne suivante.
3
Weike