Administration Linux

grep, sed et awk : des pipelines de traitement de texte qui répondent aux vraies questions

Intermédiaire10 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

Trois outils couvrent presque tout le traitement de texte en ligne de commande : grep sélectionne les lignes, sed les modifie, awk calcule sur les champs. Ce guide pose les bases communes des expressions régulières, donne à chaque outil la profondeur qu'il mérite, ajoute les petits utilitaires qui assemblent les pipelines, puis répond pas à pas à des questions réalistes sur un log de serveur web, de la première correspondance au classement final.

Ce que vous allez apprendre

  • Écrire des expressions régulières basiques et étendues, et savoir quel outil accepte quel dialecte
  • Sélectionner, compter et extraire du texte avec grep et ses options les plus utiles
  • Modifier un flux avec sed grâce aux adresses, aux options de substitution et à l'édition sur place
  • Calculer des synthèses par champ avec awk à l'aide de motifs, d'actions, de tableaux et des blocs BEGIN/END
  • Combiner cut, sort, uniq, tr et join en pipelines qui répondent à des questions sur des fichiers de log

Le traitement de texte en ligne de commande rapporte énormément pour un faible investissement. Sur un système Linux, chaque fichier de log, chaque export CSV, chaque fichier de configuration et chaque sortie de commande est du texte, et trois programmes — grep, sed et awk — couvrent presque tout ce que vous voudrez en faire. Ils sont stables depuis des décennies, présents sur toutes les machines, y compris le conteneur le plus dépouillé, et une fois que vous savez les combiner, vous cessez d'écrire des scripts jetables pour des questions auxquelles un pipeline d'une ligne répond.

Autant poser d'emblée la répartition des rôles, car c'est en prenant le mauvais outil qu'on obtient des pipelines laids. grep décide quelles lignes. sed change ce que dit une ligne. awk traite une ligne comme des champs et sait compter, additionner et mémoriser. Ce guide pose les bases d'expressions régulières qu'ils partagent, les aborde l'un après l'autre, puis termine en répondant à des questions réalistes sur un log d'accès de serveur web.

Les expressions régulières sous-jacentes

Une expression régulière décrit un ensemble de chaînes. La syntaxe est largement commune, avec une bifurcation importante.

ConstructionCorrespond à
.n'importe quel caractère unique
[abc], [a-z], [^0-9]un caractère appartenant, ou non, à un ensemble
[[:digit:]], [[:space:]], [[:alpha:]]classes de caractères POSIX, sensibles à la locale
^, $début et fin de ligne
*zéro ou plusieurs occurrences de l'élément précédent
\+, \? (BRE) ou +, ? (ERE)une ou plusieurs ; zéro ou une
\{2,5\} (BRE) ou {2,5} (ERE)entre deux et cinq répétitions
\(…\) (BRE) ou (…) (ERE)un groupe de capture, référencé ensuite par \1
`\(BRE) ou|` (ERE)
\b, \<, \>limites de mot (extension GNU)

Les expressions régulières basiques (BRE) sont le mode par défaut de grep et sed ; les étendues (ERE) s'activent avec -E et sont celles qu'awk utilise nativement. La seule différence tient aux caractères qui exigent une barre oblique inverse, et c'est l'ERE — où (a|b)+ signifie bien ce qu'on croit — qu'il faut adopter comme standard. Les expressions compatibles Perl, avec lookahead et quantificateurs non gourmands, sont disponibles via grep -P sur les systèmes GNU, mais elles sont absentes de BSD et de macOS : évitez-les dans les scripts portables.

Deux habitudes évitent l'essentiel des confusions. Mettez toujours un motif entre apostrophes pour que le shell ne développe pas *, $ ou \ avant que l'outil ne les voie. Et rappelez-vous que ces moteurs sont gourmands (greedy) : .*" va jusqu'au dernier guillemet de la ligne, pas au premier. Quand cela pose problème, utilisez plutôt une classe négative — [^"]*" s'arrête au premier. Construire une expression de façon interactive sur un texte d'exemple, dans le testeur de regex ou avec grep --color, est bien plus rapide que de raisonner de tête.

grep : choisir les lignes

grep -i 'timeout' app.log                  # case-insensitive
grep -n 'ERROR' app.log                    # prefix each match with its line number
grep -c 'ERROR' app.log                    # count matching lines, not matches
grep -v '^#' /etc/ssh/sshd_config          # invert: everything that is not a comment
grep -w 'id' schema.sql                    # whole word only: not "uuid" or "identity"
grep -F 'a.b.c' hosts.txt                  # fixed string: no regex interpretation
grep -r --include='*.java' 'TODO' src/     # recursive, filtered by name
grep -l 'password' /etc/myapp/*            # list only the file names that match
grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' access.log   # print only the matched part

Quatre options pèsent plus que les autres. -E vous donne le dialecte lisible. -o n'affiche que le texte trouvé, ce qui transforme grep en extracteur plutôt qu'en filtre. -C 3 (ou -A/-B) affiche des lignes de contexte autour de chaque résultat : c'est ainsi qu'on lit une stack trace retrouvée par le nom de son exception. Enfin -q n'affiche rien et positionne simplement le code de retour, ce dont un script a besoin :

if grep -q '^PasswordAuthentication no' /etc/ssh/sshd_config; then
  echo 'password login is disabled'
fi

grep -f patterns.txt data.txt lit de nombreux motifs depuis un fichier, un moyen rapide de filtrer une liste par une autre. Et sur un gros fichier, préfixer la commande par LC_ALL=C peut la rendre plusieurs fois plus rapide, car le moteur travaille alors sur des octets plutôt que sur des caractères multi-octets — sans risque dès lors que vos données sont en ASCII.

sed : modifier un flux

sed lit son entrée ligne par ligne, applique un script à chacune et affiche le résultat. La commande de loin la plus courante est la substitution, s/motif/remplacement/options.

sed 's/localhost/db.internal/' app.conf          # first occurrence per line
sed 's/localhost/db.internal/g' app.conf         # every occurrence
sed 's/DEBUG/TRACE/gi' app.log                   # g and i combine
sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/' dates.txt   # reorder with groups
sed 's#/var/www/old#/var/www/new#g' nginx.conf   # any delimiter: # avoids escaping slashes
sed -E 's/(password=)[^ ]+/\1REDACTED/' app.log  # mask a secret before sharing a log

Dans le remplacement, & représente la correspondance entière et \1 … \9 les groupes de capture. GNU sed ajoute \U, \L et \E pour changer la casse.

Chaque commande peut être restreinte à une adresse — un numéro de ligne, une plage ou un motif :

sed -n '10,20p' large.log                        # -n suppresses output; p prints: lines 10-20
sed '1d' data.csv                                # delete the header row
sed '/^$/d' notes.txt                            # delete blank lines
sed '/^#/d; /^$/d' /etc/nginx/nginx.conf         # comments and blanks: two commands, semicolon
sed -n '/BEGIN CERT/,/END CERT/p' bundle.pem     # everything between two patterns
sed '/\[database\]/,/^\[/ s/^port =.*/port = 5433/' app.ini   # substitute only inside a section
sed '$a\ExtraLine' file.txt                      # append after the last line
sed -n '/exception/I{=;p}' app.log               # print line numbers and lines, case-insensitive

Ce dernier groupe — une plage d'adresses combinée à une substitution — est ce qui rend sed vraiment utile pour les fichiers de configuration : il permet de modifier une clé uniquement dans la section à laquelle elle appartient.

L'édition sur place se fait avec -i, et la forme sûre fournit un suffixe de sauvegarde :

sed -i.bak -E 's/^#?(PermitRootLogin) .*/\1 no/' /etc/ssh/sshd_config
diff /etc/ssh/sshd_config.bak /etc/ssh/sshd_config

Lancez toujours la commande sans -i d'abord et regardez la sortie. Sachez aussi que sed -i écrit un nouveau fichier puis le renomme par-dessus l'ancien, si bien que l'inode change : le propriétaire peut être réinitialisé, les liens physiques sont rompus, et un processus qui garde l'ancien fichier ouvert continue d'en lire l'ancien contenu. Pour les fichiers lus par un service en cours d'exécution, c'est généralement sans conséquence ; pour tout ce qui est plus délicat, modifiez une copie et installez-la explicitement.

sed dispose d'un jeu de commandes plus large — y pour la translittération, N pour charger la ligne suivante, les commandes de hold space h, H, x, G pour le travail multi-lignes. Elles existent et sauvent parfois la mise, mais un programme sed multi-lignes est du code en écriture seule. Dès que vous avez besoin du hold space, c'est presque certainement awk qu'il vous faut.

awk : champs, motifs et arithmétique

awk découpe chaque ligne d'entrée en champs sur les blancs (ou sur le séparateur de votre choix) et lui applique une suite de règles pattern { action }. $1 est le premier champ, $NF le dernier, $0 la ligne entière, NR le numéro d'enregistrement et NF le nombre de champs.

$ awk '{ print $1, $9 }' access.log | head -3
203.0.113.7 200
198.51.100.44 404
203.0.113.7 200

$ awk -F: '$3 >= 1000 { print $1, $6 }' /etc/passwd
alice /home/alice
bob /home/bob

Sans motif, l'action s'exécute sur chaque ligne ; sans action, c'est print $0 qui s'applique par défaut, ce qui fait d'awk '/error/' un substitut fonctionnel à grep.

BEGIN s'exécute avant toute entrée, END après la dernière ligne — l'endroit naturel pour les en-têtes et les totaux :

awk -F, 'NR > 1 { total += $3; n++ }
         END { printf "%d rows, average %.2f\n", n, total / n }' sales.csv

Les tableaux d'awk sont associatifs, indexés par des chaînes, et c'est là qu'il distance les autres outils. Compter les occurrences de n'importe quoi tient en un programme de deux lignes :

awk '{ count[$1]++ } END { for (ip in count) print count[ip], ip }' access.log | sort -rn | head

Quelques constructions supplémentaires couvrent l'essentiel des usages réels :

# Sum bytes served per status code
awk '{ bytes[$9] += $10 } END { for (s in bytes) printf "%s %.1f MB\n", s, bytes[s]/1048576 }' access.log

# Requests slower than one second, with their path
awk '$NF > 1.0 { printf "%6.2fs  %s\n", $NF, $7 }' timing.log

# Field separator that is a regular expression, and output separator
awk -F'[[:space:]]+' -v OFS='\t' '{ print $2, $5 }' report.txt

# Multi-line records: a blank line separates each block
awk -v RS='' -v FS='\n' '/state: failed/ { print $1 }' units.txt

# Pass a shell variable in properly, rather than splicing it into the program text
threshold=500
awk -v t="$threshold" '$3 > t { print }' metrics.tsv

-v est la bonne façon de transmettre des valeurs du shell à un programme awk : les insérer dans une chaîne entre apostrophes ouvre la porte aux bugs de quoting et, si la valeur vient de l'extérieur, à l'injection de code. Notez aussi qu'awk possède de vraies fonctions de chaînes — length(), substr(), split(), gsub(), match(), sprintf() — et que gawk ajoute strftime() et systime() pour la gestion des dates. Un programme awk assez long pour mériter son propre fichier se lance avec awk -f report.awk data.txt.

Les seconds rôles

OutilCe qu'il fait
cutsélectionne des champs ou des plages d'octets : cut -d: -f1,6 /etc/passwd, cut -c1-8
sorttrie les lignes : -n numérique, -r inverse, -k2,2 selon un champ, -u sans doublons, -h tailles lisibles, -t séparateur
uniqfusionne les doublons adjacents : -c compte, -d seulement les doublons, -u seulement les uniques
trremplace ou supprime des caractères : tr 'A-Z' 'a-z', tr -d '\r', tr -s ' '
wccompte lignes, mots et octets : wc -l
head / tailpremières ou dernières lignes ; tail -f suit un fichier qui grossit
pastejuxtapose des fichiers en colonnes
joinjointure relationnelle de deux fichiers triés sur un champ commun
commcompare deux fichiers triés : lignes propres au premier, propres au second, communes
column -taligne une sortie séparée par des blancs en colonnes lisibles
nl, rev, fold, expandnumérotent, inversent et replient les lignes, et convertissent les tabulations en espaces

Celui qui piège tout le monde, c'est uniq : il ne compare que des lignes voisines, d'où l'idiome sort | uniq -c, alors que uniq seul sur une entrée non triée ne fait silencieusement presque rien. La séquence sort | uniq -c | sort -rn — regrouper, compter, classer — est le cheval de bataille de l'analyse de logs improvisée.

join mérite d'être plus utilisé qu'il ne l'est. À partir de deux fichiers triés sur la même clé, il réalise la même opération qu'une jointure en base de données, ce qui est pratique pour croiser un export avec une table de correspondance :

sort -t, -k1,1 users.csv > /tmp/u.csv
sort -t, -k1,1 orders.csv > /tmp/o.csv
join -t, -1 1 -2 1 /tmp/u.csv /tmp/o.csv

Si vous recourez souvent à join sur de gros fichiers, c'est le signe que ces données ont leur place dans une base de données, où les principes des bonnes pratiques SQL s'appliquent pleinement.

Des pipelines qui répondent à des questions

Prenons un log d'accès standard au format combined, où le champ 1 est l'adresse du client, le 7 le chemin, le 9 le statut et le 10 la taille de la réponse.

Quels clients ont émis le plus de requêtes ?

awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -20

Combien de requêtes par code de statut, et quelle part du total ?

awk '{ c[$9]++; n++ } END { for (s in c) printf "%-5s %6d  %5.1f%%\n", s, c[s], 100*c[s]/n }' \
  access.log | sort -k2 -rn

Quelles URL renvoient une 500, et à quelle fréquence ?

awk '$9 == 500 { print $7 }' access.log | sort | uniq -c | sort -rn | head

Comment le trafic varie-t-il selon l'heure ? Le champ d'horodatage ressemble à [07/Sep/2026:14:32:01, on le découpe donc sur les deux-points :

awk '{ split($4, t, ":"); hour[t[2]]++ }
     END { for (h = 0; h < 24; h++) printf "%02d %s\n", h, hour[sprintf("%02d", h)] + 0 }' access.log

Total des octets servis par client, du plus gros au plus petit, en mégaoctets :

awk '{ b[$1] += $10 } END { for (ip in b) printf "%10.2f MB  %s\n", b[ip]/1048576, ip }' \
  access.log | sort -rn | head

Quelles erreurs sont apparues aujourd'hui alors qu'elles n'existaient pas hier ?

grep -o 'ERROR .*' today.log   | sort -u > /tmp/today
grep -o 'ERROR .*' yesterday.log | sort -u > /tmp/yesterday
comm -23 /tmp/today /tmp/yesterday

Suivre un log en direct, ne garder que les requêtes lentes et masquer les jetons :

tail -F /var/log/app/access.log \
  | awk '$NF > 2.0' \
  | sed -E 's/(token=)[A-Za-z0-9._-]+/\1REDACTED/g'

Remarquez tail -F plutôt que -f : la majuscule rouvre le fichier après une rotation des logs, c'est-à-dire précisément au moment où vous pouvez le moins vous permettre que votre surveillance devienne muette. La même distinction compte dans la discussion sur la rotation de logs et dépannage.

Construisez ces pipelines progressivement. Lancez la première étape, regardez dix lignes, ajoutez l'étape suivante, regardez à nouveau. Déboguer un pipeline de cinq étapes écrit d'un seul jet est bien plus difficile que de le faire grandir étape par étape, et insérer | head -5 pendant que vous travaillez garde un retour instantané même sur un gros fichier.

Choisir le bon outil

Une règle de décision approximative :

  • Vous voulez seulement savoir si quelque chose est présent, ou voir les lignes ? grep.
  • Vous devez modifier le texte de chaque ligne, ou supprimer ou afficher une plage ? sed.
  • Il vous faut des colonnes, de l'arithmétique, des regroupements, de la mise en forme ou un état d'une ligne à l'autre ? awk.
  • Il faut aller vite sur un fichier énorme ? Placez le grep le plus sélectif en premier pour que les étapes suivantes voient moins de données, et préfixez avec LC_ALL=C.
  • Vous manipulez des données structurées — JSON, YAML, XML ? Utilisez un parseur. jq, yq et xmlstarlet existent précisément parce que les expressions régulières ne savent pas analyser de façon fiable les formats imbriqués, et un pipeline qui fait du grep sur du JSON cassera le jour où l'ordre des champs changera. Il en va de même pour le HTML.

Erreurs fréquentes

  • Motifs sans apostrophes. Le shell développe d'abord * et $ ; mettez chaque motif entre apostrophes.
  • uniq sans sort. Seuls les doublons adjacents sont fusionnés, donc les comptes sont faux.
  • Un .* gourmand qui avale la ligne. Utilisez une classe négative comme [^"]* pour vous arrêter au premier délimiteur.
  • grep | awk '{print $1}'. Le grep est superflu : awk '/pattern/ { print $1 }' fait les deux en une seule passe.
  • sed -i sans sauvegarde ni essai à blanc. Affichez d'abord le résultat, puis modifiez avec -i.bak.
  • Insérer des variables du shell dans un programme awk. Passez-les plutôt avec -v.
  • Analyser du JSON ou du XML avec des expressions régulières. Utilisez jq ou un vrai parseur ; l'imbrication met le moteur de regex en échec.
  • Oublier que cut -f exige un délimiteur d'un seul caractère. Pour des suites de blancs, utilisez awk, ou normalisez d'abord avec tr -s ' '.

Points clés à retenir

  • grep sélectionne les lignes, sed les transforme, awk calcule sur les champs : adapter l'outil à la question, c'est garder des pipelines courts.
  • Apprenez les expressions régulières étendues (grep -E, sed -E, awk nativement) et mettez toujours les motifs entre apostrophes.
  • grep -o, -c, -q, -v et -C transforment un filtre en extracteur, en compteur, en test et en visionneuse de contexte.
  • Les adresses de sed — numéros de ligne, plages et plages de motifs — permettent de modifier exactement une section d'un fichier de configuration ; -i.bak après un essai à blanc met l'opération à l'abri.
  • Les tableaux associatifs d'awk, avec les blocs BEGIN/END, répondent en une seule passe aux questions de comptage, de somme et de regroupement ; transmettez les valeurs du shell avec -v.
  • sort | uniq -c | sort -rn est l'idiome de classement ; comm et join comparent et croisent des fichiers triés.
  • Construisez les pipelines une étape à la fois, placez le filtre le plus sélectif en premier, et passez à un vrai parseur pour le JSON, le XML et le HTML.

Sources et lectures complémentaires

Questions fréquentes

Quand utiliser grep, quand sed et quand awk ?
Utilisez grep pour décider quelles lignes vous intéressent, sed pour transformer des lignes par substitution, et awk dès que la réponse exige de découper une ligne en champs, de faire de l'arithmétique ou d'accumuler un état d'une ligne à l'autre. Si vous vous retrouvez à enchaîner plusieurs commandes sed avec cut et un compteur, l'ensemble tient généralement en un court programme awk, plus lisible et plus rapide.
Pourquoi mon expression régulière a-t-elle besoin de barres obliques inverses dans un outil mais pas dans un autre ?
Les expressions régulières basiques, le mode par défaut de grep et de sed, traitent le plus, le point d'interrogation, les accolades, les parenthèses et la barre verticale comme des caractères littéraux, et exigent une barre oblique inverse pour leur donner leur sens spécial. Les expressions régulières étendues, activées par grep -E et sed -E et utilisées nativement par awk, font l'inverse. Tenez-vous-en au dialecte étendu et vos expressions deviendront bien plus lisibles.
Peut-on lancer sed -i sans risque sur un fichier de configuration en production ?
Oui, à condition de tester d'abord et de garder une sauvegarde. Lancez la commande sans -i et lisez la sortie, puis utilisez -i.bak pour conserver l'original sous le nom fichier.bak. Notez que GNU sed remplace le fichier en renommant une copie temporaire : le propriétaire et les éventuels liens physiques peuvent donc changer. Pour un fichier critique, modifiez une copie, comparez-la avec diff et installez le résultat avec le bon propriétaire et les bons droits.
Comment faire correspondre une tabulation, ou traiter un fichier avec des fins de ligne Windows ?
Les outils GNU acceptent les séquences d'échappement \t et \r dans les expressions régulières, et awk les comprend aussi dans les chaînes. Les fichiers venus de Windows portent un retour chariot final qui fausse la correspondance du dernier champ de chaque ligne. Supprimez-les une fois pour toutes avec sed -i 's/\r$//' fichier, ou définissez le séparateur de champs pour qu'awk les ignore, plutôt que de traquer des caractères invisibles pendant une heure.