4 points par GN⁺ 2023-08-29 | 1 commentaires | Partager sur WhatsApp
  • GNU awk parcourt automatiquement stdin et les fichiers, et permet de traiter du texte en CLI en combinant filtrage, substitution et traitement de champs dans des commandes d’une ligne
  • /regexp/ et !/regexp/ sont des syntaxes abrégées qui testent la ligne d’entrée courante et, si la condition est vraie, $0 est affiché par défaut
  • sub ne remplace que la première occurrence, tandis que gsub remplace toutes les occurrences ; si la cible est omise, la substitution s’applique à la ligne d’entrée courante $0
  • Grâce à la séparation en champs basée sur les espaces, ainsi qu’à $N, NF et $NF, awk est bien adapté aux traitements fondés sur les champs, comme extraire certaines colonnes ou appliquer des conditions
  • En enchaînant des fragments cond{action} et en ajoutant BEGIN{} et END{}, on peut aller au-delà d’un simple filtre et écrire de petits programmes avec chaînes, nombres et tableaux associatifs

Mode d’exécution de awk

  • awk peut filtrer du texte comme grep ou sed, tout en offrant des capacités de programmation pour des traitements plus complexes
  • L’entrée peut venir de stdin ou de fichiers, et est parcourue par défaut ligne par ligne en appliquant conditions et actions
  • La ligne d’entrée complète est accessible via la variable spéciale $0
    • Le terme exact est record d’entrée, mais ce chapitre l’explique en se limitant aux lignes

Filtrage et affichage par défaut

  • Les expressions régulières s’écrivent sous la forme /.../, et on utilise la syntaxe string ~ /regexp/ pour vérifier si une chaîne correspond à l’expression régulière
  • La condition inverse s’écrit string !~ /regexp/
  • Si la chaîne à tester est omise, c’est $0 qui est utilisé
  • S’il n’y a qu’une condition sans action, alors $0 est affiché automatiquement quand la condition est vraie
    • awk '/regexp/' est une forme abrégée de awk '$0 ~ /regexp/{print $0}'
    • awk '!/regexp/' est une forme abrégée de awk '$0 !~ /regexp/{print $0}'
  • Les exemples montrent le flux de filtrage de base en n’affichant que les lignes contenant at, ou celles qui ne contiennent pas e

L’affichage idiomatique avec 1

  • Dans une expression conditionnelle, un nombre non nul et une chaîne non vide sont évalués comme vrais
  • awk '1' est une écriture idiomatique qui fournit une condition toujours vraie pour chaque ligne d’entrée
  • En l’absence d’action, une condition vraie entraîne l’affichage de $0, donc awk '1' affiche toute l’entrée telle quelle
  • Le résultat est identique à awk '{print $0}' ou simplement à cat

Substitution : sub et gsub

  • awk fournit les fonctions sub et gsub pour la recherche et le remplacement
  • sub(/:/, "-") ne remplace que le premier : de chaque ligne d’entrée par -
  • gsub(/:/, "-") remplace tous les : de chaque ligne d’entrée par -
  • Les deux fonctions prennent en premier argument l’expression régulière à faire correspondre, et en second la chaîne de remplacement
  • Si la chaîne d’entrée n’est pas fournie séparément, la cible par défaut est $0
  • Si la substitution réussit, l’entrée ciblée est modifiée
  • Le 1 après le bloc de substitution est interprété comme une expression conditionnelle hors du bloc, ce qui provoque le réaffichage du $0 modifié
    • awk '{sub(/:/, "-")} 1' donne le même résultat que awk '{sub(/:/, "-"); print $0}'
    • Même avec print seul, la cible affichée par défaut reste $0

Comment choisir entre grep, sed et awk

  • Pour un simple filtrage de lignes, grep, optimisé pour cet usage, peut être plus rapide que sed ou awk
  • Pour les remplacements, sed peut être plus rapide que awk
  • Les fonctionnalités des outils ne se correspondent pas toujours exactement en 1:1
    • Implémenter grep -o avec sed ou awk demande davantage d’étapes
    • Seul grep fournit la recherche récursive
  • Une discussion connexe est disponible sur unix.stackexchange: When to use grep, sed, awk, perl, etc

Traitement fondé sur les champs

  • awk est particulièrement souvent utilisé pour les traitements fondés sur les champs
  • Par défaut, il découpe chaque ligne d’entrée selon les espaces, et chaque champ est accessible via $N
    • $2 désigne le deuxième champ
    • $NF désigne le dernier champ
    • NF désigne le nombre total de champs dans la ligne d’entrée courante
  • L’exemple table.txt montre de manière compacte des traitements par colonnes
    • afficher le deuxième champ de chaque ligne
    • n’afficher que les lignes dont le dernier champ est négatif
    • remplacer b par B uniquement dans le premier champ
  • Le fichier d’exemple se trouve dans le répertoire example_files

Structure d’une commande d’une ligne

  • Une commande awk d’une ligne suit généralement la forme suivante
awk 'cond1{action1} cond2{action2} ... condN{actionN}'
  • S’il n’y a pas de condition, l’action est toujours exécutée
  • S’il n’y a pas d’action, $0 est affiché quand la condition est vraie
  • À l’intérieur d’un bloc, plusieurs instructions peuvent être séparées par des points-virgules ;
  • En enchaînant plusieurs blocs, on peut exprimer des actions conditionnelles dans la plupart des commandes d’une ligne sans if explicite
    • awk '$NF<0' table.txt n’affiche que les lignes dont le dernier champ est négatif
  • BEGIN{} s’exécute avant la lecture des entrées
  • END{} s’exécute une fois que tout le traitement des entrées est terminé
  • Pour plus de détails sur les opérateurs et les expressions conditionnelles, voir gawk manual: Operators et gawk manual: Truth Values and Conditions

Chaînes et nombres

  • awk détermine le type des valeurs selon le contexte afin de permettre des solutions courtes en CLI
  • Les littéraux de chaîne s’écrivent entre guillemets doubles
  • Les nombres peuvent être entiers, à virgule flottante ou en notation scientifique
  • BEGIN{} peut aussi être utilisé pour exécuter un programme awk sans entrée externe
  • Les variables peuvent stocker des nombres et des chaînes
    • Exemple : a=5; b=2.5; print a+b
    • Mettre des chaînes côte à côte les concatène
  • Les variables non initialisées se comportent comme une chaîne vide en contexte chaîne, et comme 0 en contexte numérique
  • Si une chaîne est utilisée dans une expression numérique, elle est convertie de force en nombre
    • Si la chaîne ne commence pas par un nombre valide, elle est traitée comme 0
    • Les espaces en début de chaîne sont ignorés
  • Concaténer une chaîne à un nombre convertit le nombre en chaîne
  • Pour plus de détails, voir gawk manual: Constant Expressions et gawk manual: How awk Converts Between Strings and Numbers

Tableaux

  • Les tableaux awk sont des tableaux associatifs et fonctionnent comme des paires clé-valeur
  • Les clés peuvent être numériques ou textuelles, mais les clés numériques sont converties en chaînes en interne
  • Des tableaux multidimensionnels peuvent aussi être utilisés
  • Dans l’exemple, des valeurs sont stockées et lues via des clés textuelles comme student["id"] et student["name"]
  • La présence d’une clé se teste avec une forme comme "id" in student
  • Pour plus de détails, voir gawk manual: Arrays

Exercices et suite

  • Ce chapitre présente brièvement les raccourcis de syntaxe de awk, le filtrage, la substitution, le traitement de champs, la conversion de types et les tableaux
  • Le chapitre suivant traite des expressions régulières, et les fonctionnalités présentées ici continueront d’être utilisées dans les exemples
  • Vous pouvez vous familiariser avec la syntaxe en changeant les numéros de champ, ou en testant d’autres entrées comme des numéros de champ négatifs ou à virgule flottante
  • L’application TUI d’exercices interactifs peut être installée depuis le dépôt AwkExercises, et son mode d’emploi figure dans app_guide.md
  • Tous les exercices sont rassemblés dans Exercises.md, et les solutions dans Exercise_solutions.md

1 commentaires

 
GN⁺ 2023-08-29
Avis de Hacker News
  • J’aime awk et je l’utilise assez souvent ; l’un de mes usages principaux est de m’en servir comme d’un sed avec état.
    Par exemple, pour ne faire correspondre une ligne que lorsqu’elle apparaît après une certaine ligne précédente, ce qui est utile pour fabriquer des linters temporaires.
    Récemment, j’ai créé une vérification pour trouver des fichiers de migration contenant des CREATE INDEX potentiellement problématiques sur de très grandes tables, parce qu’ils étaient présents sans CONCURRENTLY ; comme les instructions SQL peuvent s’étendre sur plusieurs lignes, une simple correspondance était difficile.
    awk permet de suivre l’état sur plusieurs lignes, du genre « on est dans une instruction create » ou « on est en train de créer un index », si bien qu’un script bricolé rapidement tourne correctement depuis environ un an.

    • Il faudra bien que j’apprenne awk un jour, mais entre-temps j’ai appris des fonctionnalités basées sur l’état plus avancées de sed.
      Pour n’afficher la ligne courante que lorsque la ligne précédente correspond à un certain motif, on peut faire quelque chose comme sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'.
      Exemple : echo -e "PREV\nCURR\nCURR\nCURR\nPREV\nRED" | sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}' n’affiche que CURR.
      Cela utilise le hold buffer de sed ; -n désactive l’affichage par défaut, puis p n’affiche que les lignes nécessaires.
      x échange la ligne courante avec le hold buffer ; dans le bloc /PREV/ { ... }, on échange à nouveau, puis on n’affiche que si la ligne courante contient CURR.
      Le dernier x sert à revenir en arrière au cas où les correspondances se chevaucheraient.
      Bien sûr, il y a de fortes chances qu’un script awk soit beaucoup plus simple et direct, mais on peut aussi faire ça avec sed.
      Le temps passé à apprendre cela aurait peut-être été mieux investi dans awk, mais le tutoriel https://www.grymoire.com/Unix/Sed.html est tellement bon que j’y ai appris presque tout ce que je sais sur sed.
    • Ce serait bien si tu pouvais partager un exemple de suivi de l’état de SQL avec awk.
  • Les personnes qui lisent ce fil pourraient aussi être intéressées par le « Ask HN: Share a shell script you like » d’il y a deux semaines.
    Avec 78 commentaires, ça n’a pas eu autant de succès que je l’espérais, mais cela reste une référence utile : https://news.ycombinator.com/item?id=37112991

  • Je garde un petit intérêt pour Awk, comme pour Lisp et d’autres, et en 2022 j’ai créé cppawk : https://www.kylheku.com/cgit/cppawk/about/
    cppawk ajoute des fonctionnalités de prétraitement à Awk.
    Il propose une macro de boucle prenant en charge plusieurs clauses ; on peut combiner les clauses pour créer des itérations parallèles ou des produits cartésiens, et les utilisateurs peuvent l’étendre.
    En écrivant cinq macros simples, on peut définir une nouvelle clause.
    Si vous utilisez Awk, cela peut être utile ; c’est documenté dans plusieurs pages man et accompagné de tests unitaires qui tournent avec gawk et mawk.

  • C’est peut-être mon vieux réflexe d’administrateur système qui parle, mais je ne vois pas bien l’avantage d’awk par rapport au fait d’écrire la même chose simplement en Perl.
    J’ai vu beaucoup d’horribles scripts shell écrits par des administrateurs système juniors, et à chaque fois je me disais : « la partie traitement de texte aurait été tellement plus propre en Perl ».

    • Si vous êtes dans un environnement où Perl est toujours utilisé, c’est un argument assez valable.
      Pour moi, awk fait partie des rares langages que je peux laisser de côté pendant des mois et reprendre en main en 10 minutes.
      Il a un côté intuitif et s’intègre naturellement avec les outils courants de ligne de commande.
    • Si l’on compare Awk et Perl pour écrire des scripts, je préfère Perl ou Python.
      Mais cet article porte sur de courtes commandes en une ligne à usage temporaire, et dans ce cas sed/awk est meilleur que Perl.
      Si vous connaissez déjà Perl, vous pouvez continuer à l’utiliser sans forcément apprendre un outil de plus.
    • La force d’Awk, c’est sa boucle de lecture fournie gratuitement, la séparation en champs et son modèle de correspondance par motifs/conditions.
      En tant que langage, il est « correct sans plus », mais largement utilisable.
      Bien sûr, on peut tout faire en Perl aussi, mais il faut écrire soi-même le boilerplate qu’awk vous offre gratuitement.
      Les avantages du langage Perl ne sont pas assez importants pour moi pour abandonner awk ; comme j’utilise surtout awk pour le traitement de données et des transformations ponctuelles de fichiers plutôt que pour du « scripting », la profondeur de Perl ne me manque pas.
      Si j’ai besoin de fonctionnalités plus poussées, je vais ailleurs.
    • Il n’y a pas vraiment d’avantage technique ; les fonctionnalités de Perl sont intentionnellement proches d’un sur-ensemble d’awk.
      Comme les générations actuelles n’ont pas appris Perl, elles semblent redécouvrir l’idée du traitement de flux à travers awk.
      awk était une excellente idée à la fin des années 1970 et une vraie innovation qui méritait d’être imitée.
      Ensuite, Perl l’a imitée et dépassée, mais il a été oublié ; voir awk redécouvert aujourd’hui est donc un peu embarrassant.
    • Sans sarcasme, je me demande pourquoi Perl serait meilleur que Python pour les scripts.
  • Un aspect peu connu de gawk est qu’il est généralement fourni avec des extensions utiles.
    On peut accéder à des fonctions comme readdir(), ord(), chr(), gettimeofday(), sleep().
    https://www.gnu.org/software/gawk/manual/html_node/Extension-Samples.html

  • Les commandes awk en une ligne sont vraiment puissantes
    La question difficile est de savoir s’il vaut la peine d’investir dans une programmation awk plus complexe
    Quand la tâche de traitement exige une logique compliquée, awk sait bien la fournir, mais dans un style court et obscur qui rappelle les débuts de l’informatique
    Et si l’on choisit une alternative moderne, elle apporte aussi son lot de contraintes, peut être moins intuitive, notamment comme pandas, et poser en plus des problèmes de performance

    • Pour moi, le gros problème, ce sont les bibliothèques
      Même un simple fichier de fonctions communes personnelles ne donne pas vraiment l’impression d’être bien pris en charge, et il ne semble pas y avoir de bonne manière d’importer des bibliothèques tierces
      Dès que j’ai besoin de fonctions utilitaires et que je commence à découper en plusieurs lignes, je passe généralement à Python
      Et là, le programme devient 2 à 3 fois plus gros, ce qui me fait soupirer
      Ruby est une excellente alternative à awk, mais si les collègues ne connaissent pas Ruby, il est difficile d’en attendre une maintenance
    • Avant ChatGPT, je n’aimais pas les commandes awk/sed en une ligne obscures
      Aujourd’hui, on peut coller ce genre de commandes difficiles à lire dans une IA, qui les explique assez bien étape par étape, donc ça passe mieux
      Je préfère malgré tout quelques lignes de Python en plus pour pouvoir faire des tests unitaires, mais pour des traitements rapides de données, il arrive qu’on n’ait pas besoin de tests unitaires au départ
  • Une nouvelle version de l’ebook “CLI text processing with GNU awk” est publiée
    Il permet d’apprendre pas à pas la commande GNU awk, du niveau débutant à avancé, avec des centaines d’exemples et d’exercices, et couvre en profondeur le traitement de champs, le filtrage, le traitement de plusieurs fichiers, les solutions dépendant de plusieurs enregistrements, la comparaison d’enregistrements et de champs entre fichiers, la recherche de doublons en conservant l’ordre d’entrée, les expressions régulières, etc.
    PDF/EPUB : https://learnbyexample.gumroad.com/l/gnu_awk (gratuit jusqu’au 31 août 2023)
    Version web : https://learnbyexample.github.io/learn_gnuawk/
    Sources Markdown et fichiers d’exemples : https://github.com/learnbyexample/learn_gnuawk
    Application TUI interactive pour s’entraîner : https://github.com/learnbyexample/TUI-apps/blob/main/AwkExercises
    Côté bundles, le lot Magical one-liners de commandes en une ligne grep, sed, awk, perl et ruby coûte 5 $ : https://learnbyexample.gumroad.com/l/oneliners/new_awk_release, et le bundle complet de 13 ebooks coûte 12 $ : https://learnbyexample.gumroad.com/l/all-books/new_awk_release
    Tout retour, qu’il s’agisse de coquilles, d’erreurs dans le code, de passages qui ont bien fonctionné ou non, serait très utile
    Les discussions précédentes sont https://news.ycombinator.com/item?id=15549318 et https://news.ycombinator.com/item?id=22758217

    • Je me demande combien de personnes paient réellement pour des ressources comme Magical one-liners, et si le modèle « lire d’abord, puis choisir de payer » a déjà été expérimenté
      Cela peut valoir 5 ou 15 $, mais il se peut aussi que ce ne soit pas différent de ce que j’ai déjà enregistré dans mon fichier personnel de one-liners, donc j’hésite à payer juste pour vérifier
    • On voit clairement qu’un gros travail a été fourni, et j’aime particulièrement l’application TUI
      Je me demande si la version web restera gratuite après le 31 août
  • J’avais autrefois fait un golf pour dessiner un cœur en AWK : https://gist.github.com/auselen/906a53b47a7d616b080dbef85eb8f776

  • 99,9 % de mon usage d’awk consiste à découper des lignes en supprimant les espaces consécutifs
    Exemple : echo "key: value" | awk '{print $1}'
    J’aimerais qu’il existe une alternative plus simple

    • https://github.com/c-blake/bu/blob/main/doc/cols.md vaut peut-être le coup d’œil
      C’est en Nim, mais ce n’est peut-être pas un gros obstacle, et les autres outils dans bu/ pourraient aussi vous intéresser
    • C’est aussi possible avec cut : echo "key: value" | cut -wf 2
      Reste à savoir si c’est vraiment plus simple
      Après vérification, GNU cut n’a pas de -w, c’est donc propre à BSD
      Finalement, mieux vaut utiliser awk
    • https://github.com/sstadick/hck et https://github.com/theryangeary/choose valent aussi le détour
      Ce sont tous deux des alternatives à cut/awk et prennent en charge le découpage basé sur des regex, mais, de mémoire, ils ne suppriment pas les espaces en début/fin
      Mon outil https://github.com/learnbyexample/regexp-cut est un outil similaire à cut, qui utilise awk pour fournir un découpage basé sur des regex, des index négatifs, etc., et qui gère aussi les espaces en début/fin grâce au comportement par défaut d’awk
    • Je pense que tu voulais probablement dire echo "key: value" | awk '{print $2}'
  • Il y a quelque temps, j’ai réécrit en awk un script diff2html que j’avais porté depuis bash, et pour des raisons évidentes, il était beaucoup plus rapide
    La version awk était bien plus lisible que le script bash, et en une nuit j’ai pu apprendre le langage, déboguer, comprendre les bugs et les corriger
    Je ne sais pas si c’était une manière idiomatique de faire en awk, mais j’ai vraiment trouvé que c’était un bon langage
    https://github.com/berry-thawson/diff2html/blob/master/diff2html.sh