Linux sans magie #27 — awk : comprendre les champs, les enregistrements et les premiers traitements de texte

Comprendre les bases de awk : records, champs, séparateurs, filtres, print, NF, NR et premiers calculs pour analyser des données texte sous Linux.

Après grep, cut, sort, uniq, wc et sed, nous arrivons à un outil capable de faire davantage que sélectionner ou remplacer du texte : awk.

awk lit des données ligne par ligne, découpe chaque ligne en champs, permet de tester des conditions et exécute des actions sur les lignes qui correspondent.

Son intérêt apparaît rapidement dès que l’on veut répondre à des questions comme :

  • afficher certaines colonnes ;
  • filtrer selon la valeur d’un champ ;
  • compter des enregistrements ;
  • additionner des valeurs numériques ;
  • reformater une sortie ;
  • combiner plusieurs de ces opérations dans une seule commande.

L’objectif de cet article est de comprendre le modèle mental de base de awk, pas d’en faire un cours complet de programmation.

Le modèle mental : records, champs et actions

Par défaut, awk lit son entrée sous forme de records. Dans les cas simples, un record correspond à une ligne.

Chaque record est ensuite découpé en fields, c’est-à-dire en champs.

Prenons cette ligne :

alice 12 admin

Pour awk :

$0 → alice 12 admin
$1 → alice
$2 → 12
$3 → admin

$0 représente le record complet.

$1, $2, $3 représentent les champs individuels.

La variable prédéfinie NF contient le nombre de champs du record courant.

Dans cet exemple :

NF → 3
$NF → admin

$NF désigne donc le dernier champ, quel que soit le nombre de champs présents sur la ligne.

Premier exemple avec print

Créons un petit jeu de données :

mkdir -p /tmp/linux-sans-magie-27
cd /tmp/linux-sans-magie-27

cat > utilisateurs.txt <<'EOF'
alice 12 admin
bob 7 user
claire 19 admin
david 3 user
emma 15 user
EOF

Pour afficher le premier champ :

awk '{ print $1 }' utilisateurs.txt

Résultat attendu :

alice
bob
claire
david
emma

La partie entre accolades est l’action exécutée pour chaque record.

Ici, il n’y a pas de condition : toutes les lignes sont donc traitées.

Afficher plusieurs champs

Pour afficher le premier et le troisième champ :

awk '{ print $1, $3 }' utilisateurs.txt

Résultat :

alice admin
bob user
claire admin
david user
emma user

Avec print, les éléments séparés par des virgules sont affichés avec le séparateur de sortie par défaut.

Pour ajouter du texte fixe :

awk '{ print "utilisateur:", $1, "role:", $3 }' utilisateurs.txt

Résultat :

utilisateur: alice role: admin
utilisateur: bob role: user
utilisateur: claire role: admin
utilisateur: david role: user
utilisateur: emma role: user

Cette capacité rend awk pratique pour produire rapidement une sortie lisible.

Utiliser $0, NF et $NF

Pour afficher la ligne complète :

awk '{ print $0 }' utilisateurs.txt

Cela peut sembler inutile, mais $0 devient très pratique quand il est associé à une condition.

Pour afficher le nombre de champs de chaque ligne :

awk '{ print NF, $0 }' utilisateurs.txt

Résultat attendu :

3 alice 12 admin
3 bob 7 user
3 claire 19 admin
3 david 3 user
3 emma 15 user

Pour afficher uniquement le dernier champ :

awk '{ print $NF }' utilisateurs.txt

Résultat :

admin
user
admin
user
user

$NF évite d’avoir à connaître à l’avance le numéro du dernier champ.

Filtrer avec une condition

La structure générale la plus importante à retenir est :

condition { action }

Pour afficher seulement les lignes dont le troisième champ vaut admin :

awk '$3 == "admin" { print $0 }' utilisateurs.txt

Résultat :

alice 12 admin
claire 19 admin

On peut simplifier l’action :

awk '$3 == "admin" { print }' utilisateurs.txt

Un print sans argument affiche le record complet.

Comparer des valeurs numériques

Le deuxième champ de notre fichier contient une valeur numérique.

Pour afficher les utilisateurs dont cette valeur est supérieure ou égale à 10 :

awk '$2 >= 10 { print $1, $2 }' utilisateurs.txt

Résultat attendu :

alice 12
claire 19
emma 15

La condition est évaluée pour chaque record.

Si elle est vraie, l’action entre accolades est exécutée.

Utiliser une expression régulière comme filtre

awk peut aussi filtrer selon une expression régulière.

Pour afficher les lignes contenant admin :

awk '/admin/ { print }' utilisateurs.txt

Cette forme recherche le motif dans le record complet, c’est-à-dire dans $0.

Pour cibler uniquement un champ, on peut utiliser l’opérateur ~ :

awk '$3 ~ /^adm/' utilisateurs.txt

Résultat :

alice 12 admin
claire 19 admin

Ici, seul le troisième champ est comparé à l’expression régulière.

Compter avec NR

NR est une variable prédéfinie qui contient le nombre de records lus depuis le début du traitement.

Pour numéroter les lignes :

awk '{ print NR, $0 }' utilisateurs.txt

Résultat :

1 alice 12 admin
2 bob 7 user
3 claire 19 admin
4 david 3 user
5 emma 15 user

Pour connaître le nombre total de lignes lues :

awk 'END { print NR }' utilisateurs.txt

Le bloc END est exécuté une fois, après lecture de toutes les données.

Résultat :

5

Premier calcul : additionner une colonne

awk peut mémoriser des valeurs dans des variables.

Pour additionner le deuxième champ :

awk '{ total += $2 } END { print total }' utilisateurs.txt

Résultat :

56

Le raisonnement est le suivant :

pour chaque ligne :
    total = total + $2

à la fin :
    afficher total

Cette logique permet déjà de produire des statistiques simples sans écrire un script plus lourd.

Calculer une moyenne simple

On peut combiner la somme et le nombre de records :

awk '{ total += $2 } END { print total / NR }' utilisateurs.txt

Résultat attendu :

11.2

Ce calcul suppose que toutes les lignes traitées contiennent bien une valeur numérique exploitable dans le deuxième champ.

Sur un fichier réel, il faut donc vérifier la structure des données avant d’interpréter le résultat.

Choisir un séparateur avec -F

Jusqu’ici, les champs étaient séparés par des espaces.

Prenons maintenant un fichier séparé par des deux-points :

cat > services.txt <<'EOF'
web:prod:3
api:test:2
db:prod:1
cache:test:4
EOF

Pour indiquer que : est le séparateur :

awk -F':' '{ print $1, $2, $3 }' services.txt

Résultat :

web prod 3
api test 2
db prod 1
cache test 4

L’option -F définit le séparateur utilisé pour découper chaque record en champs.

Pour afficher uniquement les services de production :

awk -F':' '$2 == "prod" { print $1 }' services.txt

Résultat :

web
db

Utiliser BEGIN pour préparer le traitement

Le bloc BEGIN est exécuté avant la lecture du premier record.

Il peut servir à initialiser des variables ou à produire un en-tête.

Exemple :

awk -F':' 'BEGIN { print "SERVICES PROD" } $2 == "prod" { print $1 }' services.txt

Résultat :

SERVICES PROD
web
db

Pour des programmes plus longs, cette structure devient vite plus lisible qu’une succession de commandes shell.

Lab : produire un petit rapport

Utilisons le fichier suivant :

cat > consommation.txt <<'EOF'
web:prod:12
api:prod:8
db:prod:20
cache:test:4
worker:test:6
EOF

Nous voulons :

  1. sélectionner les services en production ;
  2. afficher leur nom et leur valeur ;
  3. additionner leur valeur ;
  4. afficher le total à la fin.

Commande :

awk -F':' '
$2 == "prod" {
    print $1, $3
    total += $3
}
END {
    print "total:", total
}
' consommation.txt

Résultat attendu :

web 12
api 8
db 20
total: 40

Ce petit exemple montre pourquoi awk est plus qu’un simple outil d’extraction de colonnes.

Il associe :

  • une condition ;
  • une action ;
  • des champs ;
  • une variable ;
  • un calcul ;
  • un traitement final avec END.

Construire progressivement une commande awk

Comme pour les pipelines vus précédemment, évitez d’écrire directement une expression complexe.

Commencez par vérifier le découpage :

awk -F':' '{ print $1, $2, $3 }' consommation.txt

Puis vérifiez le filtre :

awk -F':' '$2 == "prod" { print $0 }' consommation.txt

Puis affichez les champs utiles :

awk -F':' '$2 == "prod" { print $1, $3 }' consommation.txt

Enfin, ajoutez le calcul :

awk -F':' '$2 == "prod" { total += $3 } END { print total }' consommation.txt

Cette progression permet de repérer rapidement l’étape qui ne produit pas le résultat attendu.

awk dans un pipeline

awk peut lire depuis stdin.

Exemple :

grep ':prod:' consommation.txt | awk -F':' '{ print $1, $3 }'

Cette commande fonctionne.

Mais awk sait déjà appliquer lui-même une condition :

awk -F':' '$2 == "prod" { print $1, $3 }' consommation.txt

Dans ce cas précis, la seconde forme est plus directe.

Cela ne signifie pas qu’il faut supprimer les pipelines. Le bon choix dépend de la lisibilité du traitement et de la responsabilité de chaque outil.

Bonnes pratiques

Commencez par identifier clairement la structure des données : une ligne correspond-elle bien à un record ? Quel est le séparateur ? Les mêmes champs sont-ils présents partout ?

Utilisez $0 lorsque vous avez besoin de la ligne complète et les références $1, $2, $NF lorsque vous travaillez sur des champs précis.

Vérifiez NF si la structure du fichier peut être irrégulière.

Par exemple, pour afficher uniquement les lignes possédant trois champs :

awk 'NF == 3 { print }' utilisateurs.txt

Pour des formats délimités, définissez explicitement le séparateur avec -F.

Gardez aussi les expressions awk courtes tant que vous travaillez directement sur la ligne de commande. Lorsqu’un programme commence à contenir de nombreuses conditions, fonctions ou blocs, un fichier de script awk peut devenir plus maintenable.

Sécurité

Les exemples de cet article lisent des fichiers et écrivent leurs résultats sur stdout.

Ils ne modifient pas directement les fichiers sources.

Comme avec les autres outils shell, une redirection peut toutefois créer ou remplacer un fichier :

awk -F':' '$2 == "prod" { print }' services.txt > services-prod.txt

Avant de rediriger vers un fichier existant important, vérifiez toujours le nom de destination.

Il faut également être prudent avec les données contenant des secrets, des identifiants ou des informations personnelles : extraire ou reformater un fichier avec awk peut rendre ces données visibles dans un terminal, un log ou un nouveau fichier.

Coûts

awk est ici utilisé comme outil local de traitement de texte.

Les manipulations présentées n’introduisent pas de coût de service ou de ressource cloud.

Limites et points de vigilance

Cet article reste volontairement sur les bases.

Nous ne couvrons pas encore :

  • les tableaux associatifs ;
  • les boucles ;
  • les fonctions utilisateur ;
  • printf en détail ;
  • les conditions complexes ;
  • les traitements multifichiers avancés ;
  • FNR ;
  • les variables FS, OFS, RS et ORS en profondeur ;
  • les différences entre les fonctionnalités POSIX awk et les extensions spécifiques à GNU gawk ;
  • les formats structurés complexes comme JSON ou YAML.

Il faut aussi éviter de considérer awk comme un parseur universel.

Un fichier CSV contenant des champs entre guillemets, des séparateurs échappés ou des retours à la ligne internes nécessite un traitement adapté à la structure réelle du format.

Rollback ou nettoyage

Le lab ne modifie pas les fichiers sources.

Pour supprimer tous les fichiers créés pendant l’exercice :

rm -rf /tmp/linux-sans-magie-27

Cette commande cible uniquement le répertoire temporaire créé explicitement pour ce lab.

Références officielles

Conclusion

Le modèle de base de awk tient en quelques notions :

record       → une unité d’entrée, généralement une ligne
$0           → le record complet
$1, $2...    → les champs
NF           → le nombre de champs
$NF          → le dernier champ
pattern      → condition
{ action }   → traitement exécuté
NR           → nombre de records lus
BEGIN / END  → actions avant et après le traitement

Avec ces éléments, awk permet déjà de filtrer, extraire, compter et calculer dans un seul traitement lisible.

C’est une base suffisante avant d’aborder ses fonctions plus avancées.