📜 AWK – Le Guide Ultime
Deep Dive : pattern { action }, $0/$1, FS, NR/NF, BEGIN/END & Tableaux Associatifs.
1. C'est quoi AWK ?
Aho, Weinberger, Kernighan. Un langage de scan et de processing de texte (logs, CSV...).
AWK UNIX2. Philosophie & Syntaxe
Le core : pattern { action }. Boucle implicite (ligne par ligne).
3. Champs & Séparateurs (FS)
$0 (ligne), $1 (champ 1), $NF (dernier). -F (CLI) vs FS (variable).
4. Patterns : BEGIN & END
Actions "Setup" (avant la 1ère ligne) et "Cleanup" (après la dernière).
BEGIN END5. 🔎 Patterns (Regex & Logique)
/regex/, $3 > 100, $1 == "ERROR", /start/,/end/ (Range).
6. Actions (Print & Printf)
{ print }, print $1, $3. printf pour le formatage (%-10s).
7. Variables (NR, NF, FNR)
Variables magiques : NR (N° Ligne), NF (Nb Champs), FNR, RS, OFS.
8. Scripts (-f script.awk)
Passer du "one-liner" à un fichier script .awk. Shebang (#!).
9. Langage (Flow Control)
AWK est un langage : if/else, for, while, next (continue).
10. Tableaux Associatifs
La "killer feature". Le "dict" de Python. count[$1]++. Agréger des données.
11. Écosystème & "Addons"
gawk (GNU), nawk. Pipeline UNIX : grep | awk | sort | uniq.
12. One-Liners & Liens
Cheat-sheet : Compter, Sommer, !seen[$0]++ (unique), Liens (GNU).
AWK est un langage de programmation "orienté-données" conçu pour le traitement de texte avancé. C'est un outil standard sur tous les systèmes UNIX/Linux.
Son nom vient de ses trois créateurs (aux Laboratoires Bell, en 1977) : Alfred Aho, Weinberger, et Kernighan (oui, le "K" de "K&R C").
AWK est un "filtre". Il lit un fichier (ou stdin) ligne par ligne, effectue des actions sur ces lignes, et imprime le résultat. Il est "Turing-complet" (c'est un vrai langage de programmation).
Cas d'Usage (Le "Pourquoi ?")
AWK est le roi quand les données sont structurées en colonnes (logs, CSV, ls -l, etc.).
- Extraire des colonnes spécifiques d'un fichier (ex: "le 1er et 3ème mot de chaque ligne").
- Filtrer des lignes basées sur une condition (ex: "lignes où la colonne 3 est > 100").
- Calculer des statistiques (ex: "faire la somme de la colonne 4").
- Agréger des données (ex: "compter le nombre d'erreurs par utilisateur").
- Formater du texte pour des rapports simples.
Toute la philosophie d'AWK tient en une seule ligne :
awk 'pattern { action }' fichier.txt
La Boucle Implicite
Pour chaque ligne du fichier.txt, AWK exécute cette logique :
- Lire la ligne.
- Le Pattern (motif) est-il VRAI pour cette ligne ?
- Si OUI, alors exécuter l'Action (le code dans
{...}). - Passer à la ligne suivante.
Variations (Toutes optionnelles)
| Syntaxe | Signification |
|---|---|
awk '{ action }' | Pattern manquant. Le pattern est "vrai" pour *chaque* ligne. (Le plus courant). |
awk '/regex/' | Action manquante. L'action par défaut est { print $0 } (imprimer la ligne entière). |
awk '/regex/ { action }' | Standard. Exécute l'action *seulement* sur les lignes qui matchent le pattern. |
Exemple (Hello World)
# Affiche "Hello" pour chaque ligne du fichier 'data.txt'
$ awk '{ print "Hello" }' data.txt
# Affiche UNIQUEMENT les lignes contenant "ERROR"
$ awk '/ERROR/' data.txt
# Affiche "Oops:" SUIVI de la ligne, SI elle contient "ERROR"
$ awk '/ERROR/ { print "Oops:", $0 }' data.txt
La "magie" d'AWK est qu'il découpe automatiquement chaque ligne en champs (colonnes). Par défaut, le séparateur est "un ou plusieurs espaces ou tabulations".
Variables de Champ
| Variable | Description |
|---|---|
$0 | La ligne entière (non modifiée). |
$1 | Le premier champ. |
$2 | Le deuxième champ. |
$NF | Le dernier champ (NF est une variable, voir 2.1). |
Exemple (ls -l)
# Commande 'ls -l' (sortie)
-rw-r--r-- 1 root root 4096 Nov 2 18:00 fichier.txt
# On veut le nom (champ 9) et la taille (champ 5)
$ ls -l | awk '{ print $9, $5 }'
# Sortie: fichier.txt 4096
Changer le Séparateur (-F ou FS)
Pour lire un CSV (séparateur virgule) ou /etc/passwd (séparateur :), on doit changer le "Field Separator" (FS).
Méthode 1 : Option -F (CLI)
Le plus simple. (Note : -F est avant l'apostrophe).
# Fichier: /etc/passwd
# root:x:0:0:root:/root:/bin/bash
# Imprimer le 1er (user) et le 7e (shell) champ
$ awk -F':' '{ print $1, $7 }' /etc/passwd
# Sortie: root /bin/bash
Méthode 2 : Variable FS (Script)
Plus puissant. On le met dans un bloc BEGIN (voir 1.4).
# Fichier: data.csv
# id,nom,prix
# 1,Pomme,1.5
$ awk 'BEGIN { FS = "," } { print $2 }' data.csv
# Sortie:
# nom
# Pomme
BEGIN et END sont deux patterns spéciaux qui ne s'exécutent pas "par ligne".
| Pattern | Quand ? | Usage typique |
|---|---|---|
BEGIN | Avant que la première ligne ne soit lue. | Initialiser des variables (sum=0), définir FS, imprimer un en-tête (Header). |
END | Après que la dernière ligne ait été lue. | Imprimer les totaux, les moyennes, ou le contenu d'un tableau associatif (voir 3.1). |
Exemple : Compter les lignes et sommer une colonne (CSV)
# Fichier: ventes.csv
# Produit,Quantite
# Pomme,50
# Poire,30
# Banane,100
$ awk '
# 1. Avant tout
BEGIN {
FS = ","
total = 0
print "--- Début du Rapport ---"
}
# 2. Pattern pour ignorer l'"header"
# (NR est le N° de ligne, voir 2.1)
NR > 1 {
# Action par ligne (ajoute la 2e colonne au total)
total = total + $2
}
# 3. Après la dernière ligne
END {
print "--- Fin du Rapport ---"
print "Total des quantités:", total
}
' ventes.csv
# Sortie:
# --- Début du Rapport ---
# --- Fin du Rapport ---
# Total des quantités: 180
Le "pattern" est la condition qui décide si l'action {...} doit s'exécuter.
1. Pattern "Regex" (/ ... /)
Le plus courant. Utilise des expressions régulières. L'action s'exécute si la ligne ($0) "matche" la regex.
# Imprime les lignes qui contiennent "ERROR" ou "WARN"
awk '/ERROR|WARN/' journal.log
# Imprime les lignes qui commencent par un "timestamp" (ex: 2025-...)
awk '/^[0-9]{4}-/' journal.log
2. Pattern Relationnel (Logique)
Une expression booléenne. Souvent utilisée pour tester la valeur d'un champ.
# Imprime les lignes où le 3e champ est > 100 awk '$3 > 100' data.txt # Imprime les lignes où le 1er champ est EXACTEMENT "root" awk -F':' '$1 == "root"' /etc/passwd # Imprime les lignes avec plus de 5 champs # (NF = Nombre de Champs, voir 2.1) awk 'NF > 5' data.txt
3. Pattern "Range" (/start/, /end/)
Un pattern très puissant et unique à AWK/sed. Il "s'active" quand il voit /start/ et "se désactive" quand il voit /end/. Parfait pour extraire des blocs.
# Fichier: # ... # START_BLOCK # Ligne A # Ligne B # END_BLOCK # ... # Imprime tout ce qui se trouve ENTRE "START_BLOCK" et "END_BLOCK" (inclus) $ awk '/START_BLOCK/, /END_BLOCK/' fichier.txt # Sortie: # START_BLOCK # Ligne A # Ligne B # END_BLOCK
L'action est le code ({ ... }) qui s'exécute quand le pattern est "vrai".
print
La commande la plus simple. Elle imprime des strings, suivis d'un "Output Record Separator" (ORS), qui est un "newline" (\n) par défaut.
# 'print' sans argument est un alias pour 'print $0'
awk '{ print }' fichier.txt # (Équivalent de 'cat')
# La virgule (,) insère le "Output Field Separator" (OFS)
# (Par défaut, un espace)
awk -F':' '{ print $1, $7 }' /etc/passwd
# Sortie: root /bin/bash
# Sans virgule, il CONCATÈNE les strings
awk -F':' '{ print $1 " -> " $7 }' /etc/passwd
# Sortie: root -> /bin/bash
printf
Pour un formatage "propre" (style langage C). Ne met PAS de "newline" (\n) automatiquement.
| Spécificateur | Description |
|---|---|
%s | String (Chaîne). |
%d (ou %i) | Entier (Decimal). |
%f | Flottant (ex: %.2f = 2 décimales). |
%-10s | String aligné à gauche (padding de 10). |
%10s | String aligné à droite (padding de 10). |
\n, \t | Newline (saut de ligne), Tabulation. |
Exemple printf
# Objectif : Formater /etc/passwd en colonnes propres
$ awk -F':' 'BEGIN { printf "%-20s | %-15s\n", "UTILISATEUR", "SHELL" } \
{ printf "%-20s | %-15s\n", $1, $7 }' /etc/passwd
# Sortie:
# UTILISATEUR | SHELL
# root | /bin/bash
# daemon | /usr/sbin/nologin
# ...
AWK fournit de nombreuses variables "magiques" (built-in) qui sont mises à jour automatiquement. Les maîtriser est la clé.
| Variable | Signification | Description |
|---|---|---|
$0, $1, ... | Champs | $0 = Ligne entière, $1 = 1er champ. |
NR | Number of Records | Le N° de la ligne *actuelle* (compteur global, commence à 1). |
NF | Number of Fields | Le nombre de champs *dans la ligne actuelle*. ($NF est donc le dernier champ). |
FNR | File Number of Records | Le N° de la ligne *dans le fichier actuel*. |
FS | Field Separator | Le séparateur d'entrée (défaut: " "). Modifié par -F ou BEGIN { FS="," }. |
OFS | Output Field Separator | Le séparateur de sortie (défaut: " "). Utilisé par print $1, $2. |
RS | Record Separator | Le séparateur d'entrée de ligne (défaut: \n). |
ORS | Output Record Separator | Le séparateur de sortie de ligne (défaut: \n). (Mis par print). |
Le "Trick" NR == FNR (Traitement multi-fichiers)
C'est la technique "pro" pour comparer deux fichiers. NR est le compteur global, FNR est le compteur du fichier actuel.
Quand AWK lit le *premier* fichier, NR et FNR sont égaux.
Quand il commence le *deuxième* fichier, FNR repart à 1, mais NR continue de compter.
Objectif : Trouver les utilisateurs de fichierA.txt qui sont *aussi* dans fichierB.txt.
# 1. Lire Fichier A (quand NR == FNR) et stocker les noms dans un tableau 'vu'
# 2. Lire Fichier B (quand NR != FNR) et imprimer si le nom est dans 'vu'
$ awk '
NR == FNR {
vu[$1] = 1 # Stocke le 1er champ dans le tableau 'vu'
next # (next = 'continue', passe à la ligne suivante)
}
$1 in vu {
print $1, "est dans les deux fichiers"
}
' fichierA.txt fichierB.txt
-f script.awk)Quand votre "one-liner" devient trop long (plus de 2-3 actions), il est temps de le mettre dans un fichier .awk.
Méthode 1 : One-Liner
$ awk -F',' 'BEGIN { total=0 } NR > 1 { total += $3 } END { print total }' data.csv
Méthode 2 : Fichier Script (-f)
On utilise -f pour spécifier le fichier script.
$ awk -F',' -f mon_script.awk data.csv
mon_script.awk
#!/usr/bin/awk -f
# (Ceci est le "Shebang", pour le rendre exécutable: chmod +x)
# 1. Bloc BEGIN (initialisation)
BEGIN {
# FS (Field Separator) est mis par le '-F' de la CLI
# OFS (Output Field Separator)
OFS = "|" # On veut une sortie en "pipe"
total_ventes = 0
# Header
print "REGION", "TOTAL_VENTES"
}
# 2. Pattern (action par ligne)
# (Ici, on ne veut que la région "Nord" et si la vente > 50)
$1 == "Nord" && $3 > 50 {
print $1, $3
total_ventes += $3
}
# 3. Bloc END (résultats)
END {
print "---", "---"
printf "TOTAL NORD: %.2f\n", total_ventes
}
AWK n'est pas juste un "filtre", c'est un langage de programmation complet (style C / Python) à l'intérieur des blocs { ... }.
if / else
# { action }
{
# Note: pas de 'elif', mais 'else if'
if ($3 > 100) {
print "Haut:", $0
} else if ($3 > 50) {
print "Moyen:", $0
} else {
print "Bas:", $0
}
}
for (Style C)
La boucle for est parfaite pour itérer sur les *champs* d'une ligne.
# Imprimer tous les champs d'une ligne, un par un
{
# (NF = Nombre de Champs)
for (i = 1; i <= NF; i++) {
print "Champ", i, "est:", $i
}
}
while & next
next est le "continue" de AWK : il arrête de traiter la ligne actuelle et passe à la suivante.
# Ignorer les commentaires
/^[ \t]*#/ {
next # (Ne pas exécuter les actions suivantes)
}
# Action "normale"
{
print "Traitement:", $0
}
C'est la fonctionnalité la plus puissante d'AWK. Les tableaux (Arrays) en AWK ne sont pas numériques (comme en C). Ce sont des tableaux associatifs (comme les dict Python ou les Map JS).
La "clé" (l'index) peut être n'importe quoi (un string, un nombre). C'est parfait pour l'agrégation.
Exemple 1 : Compter les "hits" par IP (Logs)
Objectif : Compter combien de fois chaque IP (champ 1) apparaît dans un log.
# Fichier: access.log
# 1.2.3.4 GET /
# 5.6.7.8 GET /
# 1.2.3.4 GET /page
# 1.2.3.4 POST /login
# L'incrémentation '++' crée la clé si elle n'existe pas (valeur 0)
$ awk '{ count[$1]++ } END { for (ip in count) print ip, count[ip] }' access.log
# Sortie:
# 1.2.3.4 3
# 5.6.7.8 1
Exemple 2 : Sommer les ventes par région
# Fichier: ventes.csv
# Nord,Pomme,50
# Sud,Poire,30
# Nord,Banane,100
$ awk -F',' '{
# Utilise le champ 1 (Région) comme clé
# Ajoute le champ 3 (Quantité)
ventes[$1] += $3
}
END {
# Itère sur toutes les clés ('Nord', 'Sud')
for (region in ventes) {
printf "%-10s : %.2f\n", region, ventes[region]
}
}' ventes.csv
# Sortie:
# Sud : 30.00
# Nord : 150.00
gawk vs nawk vs mawk
awk est une spécification (POSIX). Il existe plusieurs implémentations ("addons").
awk(ounawk) : Le "New AWK" (des années 80). C'est la base, présente partout.gawk(GNU Awk) : L'implémentation GNU. C'est le standard de facto sur Linux. Il a le plus de fonctionnalités (ex:printfformaté,match(),strftime(), ...).mawk(Mike's Awk) : Une implémentation ultra-rapide, mais avec moins de features quegawk.
La "Trinité" UNIX (grep, sed, awk)
AWK n'est pas seul. Il fait partie de la "chaîne d'outils" (pipeline) UNIX. La philosophie est : "Chaque outil fait une chose, et la fait bien".
| Outil | Spécialité | Cas d'usage |
|---|---|---|
grep | Filtrage de lignes (basé sur Regex). | "Trouve-moi toutes les lignes contenant 'ERROR'." |
sed | Édition de stream (Rechercher/Remplacer). | "Sur ces lignes, remplace 'ERROR' par 'WARN'." |
awk | Traitement de champs/colonnes. | "Sur ces lignes, prends la 3e colonne et fais la somme." |
Exemple de Pipeline (Le "One-Liner")
Objectif : Trouver les 5 utilisateurs qui se sont le plus connectés (sshd) depuis le log.
# 1. Filtre les lignes "Accepted password"
$ grep "Accepted password" /var/log/auth.log | \
\
# 2. Extrait le nom d'utilisateur (champ 9)
awk '{ print $9 }' | \
\
# 3. Trie les noms
sort | \
\
# 4. Compte les occurrences uniques
uniq -c | \
\
# 5. Trie numériquement (inverse) et prend le Top 5
sort -nr | head -n 5
Cheat-Sheet "One-Liners"
# 1. Imprimer la 1ère et 3ème colonne
awk '{ print $1, $3 }' fichier.txt
# 2. Imprimer les lignes de plus de 80 caractères
awk 'length($0) > 80' fichier.txt
# 3. Sommer la 1ère colonne
awk '{ sum += $1 } END { print sum }' fichier.txt
# 4. Compter les lignes (équivalent 'wc -l')
awk 'END { print NR }' fichier.txt
# 5. Imprimer les lignes uniques (équivalent 'uniq')
# (La 'magie' : 'seen[$0]' vaut 0 (false) la 1ère fois, puis 1 (true))
awk '!seen[$0]++' fichier.txt
# 6. Imprimer la 3ème colonne des lignes contenant "ERROR"
awk '/ERROR/ { print $3 }' fichier.txt
# 7. Remplacer "foo" par "bar" sur la 2e colonne (gawk)
awk '{ $2 = gensub(/foo/, "bar", "g", $2); print }' fichier.txt
