Aller au contenu

Quiz : Traiter du texte : grep, sed, awk, jq

100 Apprenti ⏱ 45 min grepsedawkjqbashlinux

Ce quiz valide le niveau 100 (Apprenti) des notions du cours Traiter du texte : grep, sed, awk, jq. Visez au moins 20 bonnes réponses sur 25 ; chaque réponse renvoie à la leçon à relire. Le niveau 200 se valide avec le lab.

Toutes les questions supposent les outils d'Ubuntu 24.04 et de Debian 13 (GNU grep 3.11, GNU sed 4.9, mawk, jq 1.7.1), la locale C.UTF-8 sauf mention contraire, et le bac à sable ~/essais-texte de la leçon 1. Les chiffres cités ont été obtenus sur ce bac à sable ; vous pouvez les retrouver.

Penser en flux et grep

1. Pour compter les lignes de chaque programme dans le journal de sig-app-2, un collègue lance awk '{ print $3 }' syslog.log | cut -d'[' -f1 | uniq -c. Il obtient des centaines de lignes, dont 19 python3, 1 CRON, 66 python3... Que s'est-il passé ?

  • a) cut a mal découpé les noms de programmes
  • b) uniq ne fusionne que les lignes identiques consécutives : sans tri préalable, chaque série de lignes voisines donne son propre compte
  • c) Le journal contient des fins de ligne CRLF
  • d) uniq -c compte des octets et non des lignes
Réponse

b. uniq compare chaque ligne à la précédente, et à elle seule. Il faut trier d'abord : ... | sort | uniq -c, qui donne ici 96 CRON, 24 kernel:, 8000 python3, 59 sshd, 4 systemd. Ajoutez | sort -rn pour classer par effectif. Leçon 1.

2. Que renvoient printf 'x 2 b\ny 2 a\n' | sort -k2 et printf 'x 2 b\ny 2 a\n' | sort -k2,2, et pourquoi diffèrent-ils ?

Réponse

sort -k2 place y 2 a en premier, sort -k2,2 place x 2 b en premier. -k2 désigne une clé qui commence au deuxième champ et va jusqu'à la fin de la ligne : on compare 2 b et 2 a. -k2,2 s'arrête à la fin du deuxième champ : les deux clés valent 2, et sort départage alors par la comparaison de la ligne entière, en dernier recours (-s désactive ce dernier recours et garde l'ordre d'arrivée). Écrivez toujours la fin de la clé. Leçon 1.

3. Que contient liste.txt après sort liste.txt > liste.txt ?

  • a) Les lignes triées
  • b) Rien : le fichier est vide
  • c) Les lignes d'origine, sort ayant refusé d'écrire
  • d) Les lignes triées, suivies des lignes d'origine
Réponse

b. Le shell prépare la redirection avant de lancer sort : il ouvre liste.txt en écriture et le tronque. sort lit ensuite un fichier vide. sort -o liste.txt liste.txt est la forme sûre : sort lit toute son entrée avant d'ouvrir le fichier de sortie. Le même piège vaut pour jq (question 25) et pour toute commande. Leçon 1.

4. Dans un script en set -o pipefail, yes | head -n 1 affiche y, puis echo $? affiche 141. Le tube a-t-il échoué ? Que signifie 141 ?

Réponse

Rien n'a mal tourné. head se termine après une ligne et ferme le tube ; à sa prochaine écriture, yes reçoit le signal SIGPIPE et meurt. 141 vaut 128 + 13, le numéro de SIGPIPE. Sans pipefail, le code du tube est celui de head (0) ; avec pipefail, c'est celui de la dernière commande en échec, ici yes (PIPESTATUS vaut 141 0). Un script qui interrompt volontairement la lecture d'un flux doit tolérer ce code pour la commande en amont. Leçon 1.

5. Sur le journal de sig-app-2, grep -c 503 syslog.log répond 144, alors que l'API n'a renvoyé que 101 réponses 503. D'où viennent les 43 autres lignes, et comment ne compter que les codes ?

Réponse

503 apparaît aussi ailleurs dans la ligne : dans les microsecondes de l'horodatage (.250349), dans le numéro d'un processus sshd (sshd[55036]), dans un identifiant de signalement (/signalements/11503). grep cherche une sous-chaîne, pas un champ. On ancre le motif sur ce qui entoure le code dans ce format : grep -c ' 503 -$' syslog.log répond 101. Plus robuste encore, on teste le champ lui-même avec awk ($3 ~ /^python3\[/ && $12 == 503). Leçon 2.

6. Un script en set -e contient n=$(grep -c 'Invalid user' "$journal"), puis affiche le nombre de tentatives. Le jour où le journal ne contient aucune tentative, que se passe-t-il ?

  • a) n vaut 0 et le script continue
  • b) Le script s'arrête sur cette ligne, sans message, avec le code 1
  • c) n est vide et le script continue
  • d) grep affiche une erreur no match
Réponse

b. grep renvoie 0 s'il a trouvé, 1 s'il n'a rien trouvé, 2 en cas d'erreur. Il affiche bien 0, mais son code 1 devient le code de l'affectation, et set -e arrête le script. « Rien trouvé » n'est pas une erreur ici : n=$(grep -c 'Invalid user' "$journal") || true garde le 0 et laisse passer, tout en perdant la distinction avec le code 2, qu'il faut alors tester explicitement si elle compte. Leçon 2.

7. Pour isoler l'incident, grep '2026-10-07T14:(0[2-9]|1[0-9])' syslog.log ne renvoie rien (code 1), alors que 150 lignes du journal de sig-app-2 tombent dans ce créneau. Pourquoi ?

  • a) Les crochets doivent être protégés par des barres obliques inverses
  • b) Sans -E, grep lit une expression basique (BRE), où (, | et ) sont des caractères ordinaires : il cherche littéralement la parenthèse et la barre verticale
  • c) grep ne sait pas chercher dans des dates
  • d) Il faut -P pour l'alternance
Réponse

b. En BRE, le groupement et l'alternance s'écrivent \(, \| et \) (le \| est une extension GNU). grep -E '2026-10-07T14:(0[2-9]|1[0-9])' trouve les 150 lignes. Retenez les deux familles de POSIX et choisissez-en une par habitude, en général -E. Leçon 2.

8. Un journal récupéré après un arrêt brutal de la machine contient des octets nuls à la fin. grep 503 syslog.log n'affiche plus les lignes, seulement grep: syslog.log: binary file matches. Que fait grep, et comment obtenir les lignes ?

Réponse

GNU grep juge un fichier binaire quand il y trouve un octet nul (ou des octets invalides dans l'encodage de la locale). Pour ne pas inonder le terminal, il remplace alors les lignes trouvées par ce message, envoyé sur la sortie d'erreur depuis grep 3.5, et renvoie quand même 0. grep -a (--binary-files=text) traite le fichier comme du texte. Sur des données hostiles, gardez en tête que -a peut envoyer des séquences de contrôle au terminal ; cat -v les rend visibles. Leçon 2.

sed

9. sed -n '/^2026-10-07T14:02:/,/^2026-10-07T14:19:59/p' syslog.log devait extraire l'incident de sig-app-2, environ 150 lignes. Il en affiche 2 987, jusqu'à la fin du fichier. Pourquoi ?

Réponse

Un intervalle d'adresses début,fin commence à la première ligne qui correspond à début et s'arrête à la première ligne suivante qui correspond à fin. Aucune ligne n'est horodatée exactement 14:19:59 : la borne de fin n'est jamais atteinte, et l'intervalle court jusqu'à la dernière ligne, sans erreur. Choisissez une borne de fin qui existe forcément (/^2026-10-07T14:2/, première ligne après 14:19), ou filtrez sur la valeur elle-même avec une expression régulière ou awk. Leçon 3.

10. Un script fait sed "s/support/$equipe/" fiche.txt avec equipe='R&D'. La ligne service = support devient :

  • a) service = R&D
  • b) service = RsupportD
  • c) service = R\&D
  • d) Une erreur de syntaxe
Réponse

b. Dans le remplacement de s, & désigne tout le texte trouvé. Une valeur insérée dans un remplacement doit être protégée : \ d'abord, puis &, le délimiteur et les sauts de ligne. Avec equipe='a/b', la commande casse (unknown option to 's') ; avec une valeur choisie par un tiers, elle peut être détournée. Mieux vaut encore refuser les valeurs hors d'une liste blanche. Leçon 3.

11. /etc/signalements/app.conf est un lien symbolique vers un fichier versionné, vrai.conf. Après sed -i 's/10/20/' /etc/signalements/app.conf :

  • a) vrai.conf est modifié, le lien reste un lien
  • b) Le lien est remplacé par un fichier ordinaire qui contient la version modifiée ; vrai.conf est inchangé
  • c) sed refuse de modifier un lien symbolique
  • d) Le lien et sa cible sont tous deux modifiés
Réponse

b. sed -i écrit un fichier temporaire dans le même répertoire puis le renomme sur le nom donné : c'est un nouveau fichier, avec un nouvel inode, qui prend la place du lien. Le dépôt ne voit plus jamais les modifications, et un lien physique vers l'ancien fichier garde l'ancien contenu. GNU sed propose --follow-symlinks ; regler-conf résout le lien lui-même avant d'écrire. Leçon 4.

12. Un collègue ajoute un réglage avec sed -i '/^\[api\]/a delai_lecture = 15' app.conf. Le script de déploiement est relancé après une coupure réseau. Que contient la section [api], et que faudrait-il ?

Réponse

Deux lignes delai_lecture = 15 : la commande a ajoute une ligne à chaque exécution, elle n'est pas idempotente. Selon le logiciel, la seconde valeur l'emporte, la première, ou le fichier est refusé. Une modification de configuration doit d'abord regarder l'état du fichier : remplacer la clé si elle existe dans la section, l'ajouter seulement sinon, puis vérifier qu'un second passage ne change rien (ni contenu, ni date de modification). C'est ce que fait regler-conf. Leçon 4.

13. Que bloque l'option --sandbox de GNU sed, et dans quel cas faut-il l'utiliser ?

Réponse

Elle désactive les commandes e (exécuter une commande du shell), r (lire un fichier) et w (écrire un fichier), ainsi que les drapeaux équivalents de s : sed refuse le script avec e/r/w commands disabled in sandbox mode et le code 1. On l'utilise dès qu'une partie du script sed vient de données (une valeur insérée, une ligne d'un fichier) : si une injection réussit malgré les protections, elle ne peut au moins ni lancer de commande ni écrire ailleurs. anonymiser-journal lance sed ainsi. Leçons 3 et 4.

awk

14. Avec la ligne sig-app-1 503, que produisent awk '{ print $1 $2 }' et awk '{ print $1, $2 }' ?

  • a) sig-app-1 503 dans les deux cas
  • b) sig-app-1503, puis sig-app-1 503
  • c) Une erreur pour la première forme
  • d) sig-app-1,503, puis sig-app-1 503
Réponse

b. Deux expressions côte à côte sont concaténées. La virgule sépare les arguments de print, qui les écrit séparés par OFS (une espace par défaut). Leçon 5.

15. Pour lister les erreurs serveur, un collègue écrit awk '$12 > 499' syslog.log sur le journal de sig-app-2. Il obtient 167 lignes, dont 24 du noyau et 36 de sshd. Pourquoi, et pourquoi $12 + 0 > 499 ne règle-t-il pas tout (139 lignes) ?

Réponse

awk compare numériquement seulement si les deux opérandes sont des nombres ou des chaînes numériques (des données qui ont l'air de nombres). Sur une ligne du noyau, le douzième champ vaut TOS=0x00 : la comparaison devient textuelle, et "TOS=0x00" > "499" est vrai dans l'ordre des caractères. Forcer le nombre avec + 0 supprime cet effet ("TOS=0x00" + 0 vaut 0), mais le douzième champ de la plupart des lignes sshd est un numéro de port, supérieur à 499 : il reste 107 lignes de l'API et 32 de sshd. Il faut d'abord sélectionner les bonnes lignes, puis tester le champ : $3 ~ /^python3\[[0-9]+\]:$/ && $12 >= 500. Leçon 5.

16. Dans l'export du 7 octobre, la ligne 12201,nid-de-poule,"Saint-Exemple, centre",2026-10-07 est lue par awk -F,. Que valent NF et $3 ?

  • a) NF = 4, $3 = Saint-Exemple, centre
  • b) NF = 5, $3 = "Saint-Exemple
  • c) NF = 4, $3 = "Saint-Exemple, centre"
  • d) awk signale une erreur de format
Réponse

b. -F, découpe sur chaque virgule, sans rien savoir des guillemets du CSV (RFC 4180). La commune est coupée en deux, la date devient le cinquième champ, et tous les comptes par commune sont faux sans erreur visible. Il faut un vrai lecteur de CSV : la fonction csv_champs en awk POSIX, FPAT ou --csv (gawk 5.3 et plus, absent de Debian 13 et Ubuntu 24.04), Miller ou le module csv de Python. Leçon 6.

17. Après if (t["x"] != "") print "vu" dans un programme awk où t était vide, combien de clés contient t ? Et après if ("y" in t) ... ?

  • a) 0 dans les deux cas
  • b) 1 après le premier test (la clé x a été créée, vide), toujours 1 après le second (in ne crée rien)
  • c) 1 puis 2
  • d) Une erreur : on ne peut pas lire une clé absente
Réponse

b. Lire un élément d'un tableau awk le crée, avec la valeur vide. Le test par comparaison fausse donc les comptes (length, boucles for (k in t)) et consomme de la mémoire. Pour savoir si une clé existe, utilisez (cle in t). Leçon 6.

18. Un programme écrit sur sig-app-1 contient PROCINFO["sorted_in"] = "@ind_str_asc" avant for (k in t). Sur sig-outils, il ne signale aucune erreur, mais ses lignes sortent dans le désordre (c b a au lieu de a b c). Pourquoi ?

Réponse

awk n'est pas le même programme sur les deux machines : gawk sur les images cloud d'Ubuntu comme sig-app-1, mawk sur une Debian minimale comme sig-outils. PROCINFO["sorted_in"] est une extension de gawk ; pour mawk, PROCINFO n'est qu'un tableau ordinaire, que personne ne lit, d'où l'absence d'erreur. L'ordre de for (k in t) n'étant garanti par aucune implémentation, la solution portable est d'envoyer les lignes à sort (print ... | "LC_ALL=C sort", puis close()). Leçons 5 et 6.

19. Sous LC_ALL=fr_FR.UTF-8, mawk 'BEGIN { printf "%.2f\n", 101 / 650 * 100 }' affiche :

  • a) 15.54
  • b) 15,54
  • c) 15.538
  • d) Une erreur de locale
Réponse

b. mawk suit la catégorie LC_NUMERIC de la locale pour écrire (et lire) les nombres décimaux, comme le demande POSIX : la virgule française apparaît, et un programme qui relit ce rapport, ou un tableau Markdown comparé à une référence, ne s'y retrouve plus. gawk, lui, garde le point par défaut. Un script qui produit des données pour une machine fixe sa locale : export LC_ALL=C.UTF-8. Leçons 1 et 5.

20. Pourquoi awk "\$1 == \"$hote\" { n++ } END { print n }" journal est-il dangereux, et comment l'écrire ?

Réponse

La valeur de la variable du shell devient du code awk : un guillemet dans hote casse le programme, et une valeur choisie par un tiers peut y ajouter des instructions, jusqu'à system("..."), qui lance une commande du shell. On passe les données comme des données : awk -v hote="$hote" '$1 == hote { n++ } END { print n + 0 }', ou par l'environnement (ENVIRON["HOTE"]) quand la valeur peut contenir des barres obliques inverses, que -v interprète. Leçon 5.

jq

21. nom=$(jq '.[0].name' scw/serveurs.json) puis ssh "$nom". Pourquoi la connexion échoue-t-elle ?

  • a) scw renvoie des noms en majuscules
  • b) Sans -r, jq écrit du JSON : la variable contient "sig-app-1", guillemets compris
  • c) jq ne lit pas les tableaux
  • d) Il manque -c
Réponse

b. jq produit par défaut des valeurs JSON, donc des chaînes entre guillemets. -r (raw output) écrit les chaînes brutes, ce que l'on veut pour les passer au shell. Leçon 7.

22. jq -r '.[2].public_ips[0].address' scw/serveurs.json affiche null pour sig-outils, qui n'a pas d'IP publique, avec le code de sortie 0. Quel est le danger dans un script, et quelle option change le code de sortie ?

Réponse

Accéder à une clé ou à un indice absents donne null, sans erreur. Avec -r, null devient la chaîne null : le script tente ssh null, ou écrit null dans un rapport, et le code 0 ne dit rien. -e fait renvoyer à jq le code 1 quand la dernière sortie est null ou false (jq -e '.[0].ipv6.address' sort avec 1). On peut aussi fournir une valeur de repli explicite (// "aucune") ou filtrer avec select(. != null). Leçon 7.

23. Sur le journal JSON de sig-app-2, jq -c --arg s 503 'select(.requete.statut == $s)' api.jsonl | wc -l répond 0. Pourquoi, alors qu'il y a 101 réponses 503 ?

  • a) Les statuts sont rangés sous une autre clé
  • b) --arg passe toujours une chaîne : "503" n'est pas égal au nombre 503, et jq ne convertit pas
  • c) select ne fonctionne pas avec -c
  • d) Il faut -s pour lire plusieurs lignes
Réponse

b. jq compare sans conversion implicite : une chaîne n'est jamais égale à un nombre. --argjson s 503 passe la valeur comme du JSON, donc un nombre, et la commande trouve les 101 lignes. Autre solution : ($s | tonumber) dans le filtre. Leçon 7.

24. Un script fabrique le message d'une alerte par printf '{"commune":"%s"}\n' "$commune". Que se passe-t-il quand la commune s'appelle Saint-"Témoin", et comment l'écrire ?

Réponse

Le guillemet n'est pas échappé : le texte produit n'est plus du JSON valide (jq le refuse avec parse error, code 5), ou pire, un champ choisi par un tiers peut fermer la chaîne et ajouter ses propres clés. On laisse jq produire le JSON, en lui passant les valeurs comme des arguments : jq -n --arg commune "$commune" '{commune: $commune}', qui donne {"commune":"Saint-\"Témoin\""}. Même règle pour tout JSON produit par un script : --arg, --argjson, --rawfile, --slurpfile, jamais de concaténation. Leçon 8.

25. Pour renommer une instance dans un fichier, un collègue lance jq '.[0].name = "sig-app-1b"' serveurs.json > serveurs.json. La commande renvoie 0. Que contient le fichier, et comment faire ?

Réponse

Le fichier est vide : comme pour sort (question 3), le shell tronque serveurs.json avant de lancer jq, qui lit un fichier vide, ne produit rien et réussit. On écrit dans un fichier temporaire du même répertoire, puis on le renomme seulement si jq a réussi (jq ... serveurs.json > serveurs.json.tmp && mv serveurs.json.tmp serveurs.json), ou l'on passe par sponge de moreutils. Leçon 8.

Plan du cours

    +50 XP 100 Apprenti

    Validation sur l'honneur, enregistrée dans ce navigateur. Elle valide le niveau 100 de Traiter du texte : grep, sed, awk, jq et fait monter en rareté l'équipement de votre cosmonaute.