Awk awk awk

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

7 réflexions sur « Awk awk awk »

  1. Bonjour,
    2 petites remarques :
    1/ Les 2 « cat | » sont inutiles. Pourquoi vouloir créer un pipe qui ne sert à rien ? Donc :
    awk ‘!x[$0]++’ pas-de-doublons.txt
    2/ La 1re commande se résumé en :
    sort -u < toto.txt > pas-de-doublons.txt
    Point anecdote :
    J’ai appris à le dire ainsi :
    sort -u venant de toto.txt dans pas-de-doublons.txt
    Et le >> se lisait « dans dans » 😁

    • Les pipes viennent des billets précédents, et je n’ai extrait que cette partie pour détailler ici une partie de la commande qui m’a été recommandée par un internaute.

  2. Bonjour
    Je suis aussi pour la lisibilité avant tout, ça permet de pouvoir mieux relire, quand on a oublié ses scripts dans un coin pendant un certain temps ^.^ »

    Mais dans le cas de la première commande, il y a plus simple, et plus lisible.
    Tout simplement en faisant un sort -u toto.txt >toto_trié_sans_doublon.txt
    sort va prendre le fichier en entrée et le trier, donc pas besoin du cat,
    et l’option -u va ignorer les lignes déjà rencontré, et donc être quasiment équivalent à sort | uniq (option que j’ai découvert il y a peu)

    En tout cas, c’est toujours intéressant de voir d’autres façon de faire, ça permet entre autre une meilleure compréhension des outils que l’on peut utiliser au quotidien

    • Bonjour, je viens de tester sur mon problème particulier (fichier toto de 21Go), et c’est sans appel :
      cat toto | tr -s « \n » | sort | uniq > titi # 1h30
      cat toto | tr -s « \n » | sort -u > titi # 1h10
      cat toto | tr -s « \n » | awk ‘!x[$0]++’ | sort > titi # 15mn

      Vainqueur awk 😉

  3. Ah il m’a mangé les inferieurs !
    Donc
    awk ‘!x[$0]++’ inférieur toto.txt > pas-de-doublons.txt
    et
    sort inférieur toto.txt >pas-de-doublons.txt
    Essai inférieur <
    Avec apostrophe '<'
    Guillemets "<"
    En doublant <<

Les commentaires sont fermés.