Évaluons votre projet

Introduction à awk

Publié le 29 janvier 2021 par Eddie Barraco | outils - regex

Cet article est publié sous licence CC BY-NC-SA

Un rapide coup d’œil sur awk

J’ai récemment pris le temps de prendre en main awk. Ce formidable outil me semblait complexe et je sentais qu’il fallait y investir du temps pour s’en servir efficacement. Vous allez pourtant voir que le principe est simpliste et que quelques notions de base suffisent pour faire des chouettes choses.

Principe de base

Awk est un outil en ligne de commande qui va lire les lignes en entrée une par une et effectuer des actions suivant des instructions. Il va également séparer les différents mots des lignes pour nous permettre de les manipuler facilement.

Le format des instructions est le suivant [CONDITION] { ACTION }. Si la condition est vérifiée sur la ligne courante, alors l’action est effectuée. Les actions sont codées dans un langage proche du C. S’il n’y a pas de condition, l’action est exécutée. Une condition peut être une expression régulière ou une expression conditionnelle.

Il y a deux conditions spéciales BEGIN et END qui se déclenchent au début et à la fin du fichier.

Vous n’aviez pas les bases ! (merci Orel) Mais maintenant vous vous sentez mieux.

Quelques exemples pour vous faire saisir le principe et les subtilités.

Quelques exemples pratiques

Exemple 1

Je souhaite git restore les fichiers que j’ai supprimés par erreur dans mon espace de travail :

$ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   app/controllers/api/base_controller.rb
        deleted:    app/models/product.rb
        modified:   app/models/product_price.rb

$ git status | awk '/deleted:/ { print $2 }'
app/models/product.rb

$ git status | awk '/deleted:/ { print $2 }' | xargs -t git restore
git restore app/models/product.rb

Sur les lignes qui décrivent /deleted:/, affiche le deuxième mot, passe les résultats à git restore. Un mot est ce qui est séparé par des espaces. Ce séparateur peut être choisi avec un argument.

Exemple 2

Je souhaite compter le nombre de fichiers modifiés dans mon espace de travail.

$ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   app/controllers/api/base_controller.rb
        deleted:    app/models/product.rb
        modified:   app/models/product_price.rb

$ git status | awk 'BEGIN { count = 0 } /modified:/ { count += 1 } END { print count }'
2

Sur les lignes qui décrivent /modified:/, incrémente count. À la fin, affiche count.

Vous pouvez remarquer que cette commande donne le même résultat.

$ git status | awk '/modified:/ { count += 1 } END { print count }'
2

Exemple 3

Je souhaite transformer la sortie de git status en quelque chose de plus joli. Par exemple :

new file:
    * app/models/product_line.rb
modified:
    * app/controllers/api/base_controller.rb
    * app/models/product_price.rb
deleted:
    * app/models/product.rb
$ git status
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   app/controllers/api/base_controller.rb
        deleted:    app/models/product.rb
        new file:   app/models/product_line.rb
        modified:   app/models/product_price.rb

$ git status | awk '
$ 	# Ceci nous permet de switcher de type de modification
$ 	/modified/ { kind = "modified" }
$ 	/deleted/ { kind = "deleted" }
$ 	/new file/ { kind = "new file" }
$
$ 	# Ici on concatène nos lignes.
$ 	/\w+: +/ { files[kind] = files[kind] "  * " $NF "\n" }
$ 	# $NF est une variable spéciale qui représente le dernier mot
$
$ 	# On affiche le tout !
$ 	END {
$ 		print "new file:"
$ 		print files["new file"]
$ 		print "modified:"
$ 		print files["modified"]
$ 		print "deleted:"
$ 		print files["deleted"]
$ 	}
$ '
new:
  * app/models/product_line.rb

deleted:
  * app/models/product.rb

modified:
  * app/controllers/api/base_controller.rb
  * app/models/product_price.rb

Une version raccourcie de ce même code avec un tout petit bug à cause du mot « new file » parce qu’ils en forment en fait deux : (ça reste un exemple hein. Zut !)

$ git status | awk '
$ 	/\w+: +/ { files[$1] = files[$1] "  * " $NF "\n" }
$
$ 	END {
$ 		for ( key in files) {
$ 			print key
$ 			print files[key]
$ 		}
$ 	}
$ '
new
  * app/models/product_line.rb

deleted:
  * app/models/product.rb

modified:
  * app/controllers/api/base_controller.rb
  * app/models/product_price.rb

Ps : François a amélioré ce script et corrigé le bug que nous avons rencontré ici

Mot pour la fin

Enregistrer vos instructions courantes dans des scripts dédiés. Awk permet de les lire avec l’argument -f. Une astuce intéressante et d’écrire ses scripts ainsi :

#!/usr/bin/awk -f
# ~/bin/markdown_to_text

BEGIN { display = 1 }

/^```/ { display = !display; print ""; next }
/^---/ { display = !display; print ""; next }

display { print $0 }
!display { print "" }

On peut ensuite aisément cat mon_markdown.md | markdown_to_text ou markdown_to_text mon_markdown.md.

Ce script me permet par exemple de transformer un markdown en texte en retirant les blocs de codes et les pages additionnelles (délimitée par ---). Je l’utilise en ce moment pour valider grammaticalement cet article avec cette commande markdown_to_text awk_introduction.md | languagetool -l fr.

Awk est un outil formidable pour mettre en place des filtres et transformer du contenu. Les sorties qui ne sont pas directement utilisables programmaticalement sont facilement adaptables avec cet outil. Prenez quelques minutes pour vous intéresser à ce couteau suisse. Il vous le rendra bien !

Autres ressources utiles :


L’équipe Synbioz.
Libres d’être ensemble.