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?
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.
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'
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
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;" .
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
$ 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:
$!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
./^(.*)\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é).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.D
force sed
à passer à la commande FIRST
$!N
, mais ne lit PAS la ligne suivante du fichier ou du flux d'entrée standard.$ 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:
:loop
définit un label
nommé loop
.N
pour lire la ligne suivante dans le pattern space
.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
.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.