Article écrit par Eddie Barraco
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
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 :
- Awk man page (en)
- Introduction à awk sur grymoire.com (en)
- 20 awk examples (en)