Pour retirer les lignes en doublon dans un fichier texte contenant beaucoup de lignes (voir le billet « Dictionnaire français pour hashcat« ), j’ai utilisé la commande suivante :
cat toto.txt | sort | uniq > pas-de-doublons.txt
où toto.txt est un fichier texte d’environ 20 Go et où le fichier pas-de-doublons.txt résultant ne fait plus que 311 Mo. La commande met 1h30 à s’exécuter sur mon ordinateur.
Un internaute m’a fait remarquer en commentaire sur mon GitHub que cette commande pouvait être avantageusement remplacée par la commande suivante :
cat toto.txt | awk '!x[$0]++' > pas-de-doublons.txt
Mais avant d’essayer la commande awk sur mon ordinateur, je voulais la comprendre. Et à ma grande surprise, je n’ai pas réussi à trouver d’explications sur cet usage particulièrement concis sur internet (spoiler : j’ai mal cherché). J’ai donc demandé de l’aide sur Twitter où @CyrilBrulebois m’a fourni une explication en quelques secondes : https://unix.stackexchange.com/questions/159695/how-does-awk-a0-work
Je vais détailler ici l’explication, pour pouvoir m’y référer plus tard, quand ma mémoire me fera défaut.
awk est une commande très puissante, c’est un langage de programmation a elle tout seule qui permet une recherche de chaînes et l’exécution d’actions sur les lignes sélectionnées. Elle est utile pour récupérer de l’information, générer des rapports, transformer des données entre autres (source funix.org).
!x[$0]++ est à interpréter de la manière suivante :
$0 contient une ligne complète du fichier toto.txt
x[] est un tableau associatif. Pour en savoir plus, lire cet article sur les tableaux en bash. Il reçoit en argument autre chose que des entiers de 0 à N comme un tableau habituel. Il peut recevoir une chaîne de caractère.
x[$0] regarde si la valeur de la clé $0 est déjà présente dans le tableau associatif x[]. Si elle ne s’y trouve pas, une chaîne de caractère vide est placée dans x[$0]. Dans tous les cas, x[$0] retourne son contenu.
Lequel des deux opérateurs « ! » ou « ++ » est-il prioritaire sur l’autre ? Il s’agit de vérifier la précédence de ces deux opérateurs. Le tableau que l’on trouve par exemple dans ce document, nous indique que ++ est prioritaire sur « ! ». Mais comme il est situé à droite de x[$0] (post-incrément), il faut d’abord évaluer le contenu de x[$0], considéré comme un entier (1ère action de « ++ »), passer cette évaluation à l’opérateur « ! », puis l’incrémenter de 1 (2e action de « ++ »).
Donc si la ligne contenue dans $0 vient d’apparaître pour la première fois, x[$0] contient une chaîne de caractère vide considérée comme équivalente à 0, et x[$0] retourne 0 (= faux), puis passe à 1. Sinon x[$0] retourne N (= non nul = vrai), puis passe à N+1.
Comme !x[$0]++ est la négation de x[$0]++, il suffit d’inverser le résultat du paragraphe précédent : si la ligne contenue dans $0 vient d’apparaître pour la première fois, !x[$0] retourne 1 (= non nul = vrai), puis x[$0] passe à 1. Sinon !x[$0] retourne 0 (= faux), puis x[$0] passe à N+1.
Dit autrement, !x[$0]++ retourne :
– vrai si $0 contient une ligne non déjà vue, puis incrémente x[$0]
– faux si $0 contient une ligne déjà vue, puis incrémente x[$0]
Enfin, le comportement par défaut de awk est d’imprimer $0 en cas d’évaluation à vrai, et de ne rien faire dans le cas contraire. Toutes les lignes déjà vues disparaissent. Le fichier résultant ne possède pas de lignes en doublon.
La commande recommandée fait donc bien ce qu’elle est sensée faire, et elle le fait plus vite : 15mn sur mon ordinateur au lieu d’1h30 avec la commande « sort | uniq »…
Par contre, elle fonctionne bien parce que mon fichier texte initial contient beaucoup de doublons (le fichier final fait 311 Mo) et ne sature pas la mémoire de mon ordinateur. J’ai eu moins de chance avec un fichier texte de 15 Go recommandé par @CyrilleFranchet, et qui contient peu de doublons… Ça a fait exploser en vol ma machine. Comme quoi, la lisibilité a ses avantages ^^. Cette commande awk est donc à utiliser avec un œil sur l’évolution de l’occupation de la mémoire vive.
Pour aller plus loin : grep – sed – awk, exemples avancés
