Fiche 4 — Enchaîner les outils : pipelines et analyse de journaux

Fiche 04

TP
Le pipe, grep, wc -l, cut, sort, uniq : construire des pipelines pour analyser un fichier de logs sans jamais charger le fichier entier.
Auteur
Affiliations

Emmanuel Bruno

Université de Toulon

LIS UMR CNRS 7020

Date de publication

2026-11-04

Objectifs

À l’issue de cette fiche, vous serez capable de :

  • chaîner des commandes avec le pipe | : la sortie standard de l’une devient l’entrée standard de la suivante ;
  • filtrer un flux avec grep et le compter avec wc -l ;
  • extraire un champ d’une ligne structurée avec cut -d et -f ;
  • trier avec sort, dont le tri numérique multi-colonnes sort -k ;
  • dédoubler et agréger avec uniq et uniq -c ;
  • lire et analyser un fichier de logs de serveur web ;
  • comprendre ce qu’est un traitement en streaming : un pipeline qui ne charge pas tout le fichier.

Compétences évaluées

  • composition d’outils elementaires plutot qu’ecriture d’un programme ;
  • choix de l’ordre des maillons d’un pipeline ;
  • lecture d’une sortie de fichier de logs et extraction de ses champs ;
  • statistiques simples sur un journal : repartition des codes HTTP, clients les plus actifs ;
  • vigilance sur les correspondances erronees et sur l’ordre des donnees.
AvertissementCe que cette fiche ne fait pas

Un pipeline se lit de gauche à droite ; ce n’est pas un langage de programmation. Pas de variable, pas de boucle, pas de condition : dès qu’un traitement réclame une décision, on écrit un script (voir la fiche 5). Pas d’awk : nous restons sur cut, sort, uniq, grep, wc, et nous n’utilisons pas la syntaxe sed 's|...|...|' de la fiche 3 comme séparateur de champs.

Préparation de l’environnement de travail

Le rm -rf est borné à ce sous-répertoire ; "${WORK:?}" interdit toute suppression si la variable était vide. Cette cellule remet la fiche à zéro, donc efface tout ce que vous y aviez produit : réexécutez-la si vous avez créé ou modifié des fichiers en cours de route, puis reprenez la fiche depuis la préparation, dans l’ordre.

Si une cellule plus bas affiche un message commençant par ERREUR : — dans cette fiche presque toujours ERREUR : dossier absent —, la garde fait exit 1, ce qui termine le shell de la cellule : le moteur de calcul en redémarre un neuf, sans variable ni répertoire courant, et la cellule en échec est marquée comme exécutée, pas comme fausse. Si c’est bash: WORK: parameter null or not set qui s’affiche, la variable était déjà perdue : la garde ne s’exécute pas et le reste de la cellule tourne dans un répertoire non maîtrisé. Dans les deux cas, relancer la cellule de préparation ci-dessus — elle remet le dossier à zéro, tout ce que vous aviez produit dans ${WORK} est donc perdu — puis rejouer toutes les cellules de la fiche, dans l’ordre.

WORK=~/work/assignments/shell_tp/fiche04
rm -rf "${WORK:?}"
mkdir -p "${WORK}" || { echo "ERREUR : setup"; exit 1; }
echo "Répertoire remis à zéro : ${WORK}"

Contrôle : le pwd affiché doit se terminer par shell_tp/fiche04. Toutes les cellules qui suivent commencent par un cd défensif vers ce répertoire.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
pwd
echo "FICHE : 04_shell_P_pipelines"

Le journal à analyser : webserver.log

Toute la fiche travaille sur un seul fichier, un journal de serveur web simulé, écrit ici de façon déterministe : les mêmes lignes reviennent toujours, donc les comptes rendus donnés en attente sont exacts.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cat > "${WORK}/webserver.log" << 'EOF'
127.0.0.1 - - [2026-01-31 10:00:01] "GET /index.html HTTP/1.1" 200 1024
192.168.0.5 - - [2026-01-31 10:01:12] "GET /missing.png HTTP/1.1" 404 512
10.0.0.2 - - [2026-01-31 10:02:34] "POST /api/upload HTTP/1.1" 500 2048
127.0.0.1 - - [2026-01-31 10:03:45] "GET /about.html HTTP/1.1" 200 2048
203.0.113.4 - - [2026-01-31 10:04:56] "GET /page.html HTTP/1.1" 404 256
192.168.0.5 - - [2026-01-31 10:05:07] "GET /logo.png HTTP/1.1" 301 128
10.0.0.2 - - [2026-01-31 10:06:18] "GET /index.html HTTP/1.1" 200 1024
127.0.0.1 - - [2026-01-31 10:07:29] "GET /style.css HTTP/1.1" 200 512
203.0.113.4 - - [2026-01-31 10:08:40] "POST /api/login HTTP/1.1" 500 64
192.168.0.5 - - [2026-01-31 10:09:51] "GET /old/page HTTP/1.1" 301 256
10.0.0.2 - - [2026-01-31 10:10:02] "GET /favicon.ico HTTP/1.1" 404 128
127.0.0.1 - - [2026-01-31 10:11:13] "GET /about.html HTTP/1.1" 200 2048
203.0.113.4 - - [2026-01-31 10:12:24] "GET /index.html HTTP/1.1" 200 1024
203.0.113.4 - - [2026-01-31 10:14:46] "GET /rapport-404.pdf HTTP/1.1" 200 4096
EOF
echo "webserver.log écrit."
wc -l "${WORK}/webserver.log"

Le fichier fait 14 lignes ; le wc -l ci-dessus est votre premier contrôle. Une ligne de journal est un texte séparé par des espaces, donc numérotée ainsi par cut -d' ' -f N :

champ contenu exemple sur la ligne 1
1 adresse IP du client 127.0.0.1
2 et 3 identifiants non renseignés -
4 et 5 date et heure entre crochets [2026-01-31 puis 10:00:01]
6 méthode HTTP, précédée d’une guillemet "GET
7 chemin demandé /index.html
8 protocole, suivi d’une guillemet fermante HTTP/1.1"
9 code de réponse 200
10 taille envoyée, en octets 1024

Le contrôle ci-dessous extrait ces quatre champs sur les deux premières lignes : la sortie doit correspondre exactement à la colonne exemple du tableau, ce qui valide les indices 1, 7, 9, 10 utilisés dans toute la fiche.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
head -n 2 "${WORK}/webserver.log" | cut -d' ' -f1,7,9,10

Le pipe : la sortie de l’une devient l’entrée de l’autre

Chaque commande lit sur l’entrée standard (flux 0) et écrit sur la sortie standard (flux 1). Le pipe | branche la sortie standard de la commande de gauche sur l’entrée standard de celle de droite.

grep "404" webserver.log  |  wc -l
        |                        ^
        |                        +-- lit les lignes au fil de l'eau, n'ouvre aucun fichier
        +-- ecrit les lignes filtrees sur sa sortie standard

Rien n’est écrit sur le disque entre les deux : le flux circule.

Sans pipe, grep lit le fichier qu’on lui nomme et wc -l lit le fichier qu’on lui nomme. Avec un pipe, on ne nomme le fichier qu’une seule fois, et les étapes s’ajoutent à droite :

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "--- 1. sans pipe : grep recoit le nom du fichier ---"
grep "404" "${WORK}/webserver.log"
echo "--- 2. avec pipe : cat nourrit grep via stdin ---"
cat "${WORK}/webserver.log" | grep "404"
echo "--- 3. un maillon de plus : compter ---"
cat "${WORK}/webserver.log" | grep "404" | wc -l

Les étapes 1 et 2 produisent exactement la même sortie : cat | grep X est un réflexe coûteux, grep X fichier suffit. Ce qui compte est l’étape 3 : on allonge un pipeline au lieu de le réécrire. Le wc -l final ne connaît pas le nom du fichier, il ne voit qu’un flux.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "--- en cherchant une ligne par son numero ---"
grep -n "favicon" "${WORK}/webserver.log"
echo "--- en comptant les lignes filtrees ---"
grep "favicon" "${WORK}/webserver.log" | wc -l
NoteLes pipes se lisent de gauche à droite

Un pipeline n’est pas une commande avec des options exotiques : c’est une chaîne de petits programmes. Pour comprendre un pipeline compliqué, lisez-le maillon par maillon et demandez pour chacun : que reçoit-il sur son entrée standard, que produit-il sur sa sortie standard ? Pour en écrire un, commencez par la commande qui affiche ce que vous voulez, puis ajoutez des maillons à droite, un par un, en vérifiant la sortie à chaque fois.

Extraire un champ avant de compter : cut

Rappel de la fiche 3 : cut -d SEP -f N garde le champ N de chaque ligne en supposant un séparateur unique. Les exercices ci-dessous extraient d’abord le champ qui intéresse l’analyse (le code, l’IP, le chemin), puis le traitent.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "--- les codes HTTP de chaque ligne ---"
cut -d' ' -f9 "${WORK}/webserver.log"
echo "--- trois premieres lignes, deux champs ---"
head -n 3 "${WORK}/webserver.log" | cut -d' ' -f1,9

Trier : sort

Option Effet
-n tri numérique au lieu du tri lexicographique
-r tri inverse
-u trié et dédoublonné
-k2,2 clé de tri = champ 2 (et 2 seulement)
-k1,1nr clé de tri = champ 1, numérique, inverse
-t: séparateur de champs pour -k

Le piège par défaut : sans -n, sort compare des textes, donc des longueurs et des caractères, pas des nombres. Comparons les deux tris sur la colonne des tailles.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "--- tri lexicographique : 1024 passe avant 128 ---"
cut -d' ' -f10 "${WORK}/webserver.log" | sort -u | head -n 3
echo "--- tri numerique : 64, puis 128, puis 256 ---"
cut -d' ' -f10 "${WORK}/webserver.log" | sort -n -u | head -n 3

Pour trier selon une colonne précise, on nomme une clé. La clé -k2,2 dit : « utilise le champ 2, et rien d’autre ». Sans bornes (-k2), la clé s’étend jusqu’à la fin de la ligne.

printf 'carol 7\nalice 9\nbob 9\n' | sort -k2,2nr -k1,1
# 9 alice
# 9 bob
# 7 carol

Sans la clé secondaire -k1,1, l’ordre des lignes à égalité dépend du support d’exécution : le résultat n’est plus reproductible. C’est la règle de cette fiche : toute agrégation triée par fréquence porte une clé secondaire, pour que la sortie soit stable.

Dédoubler et agréger : uniq

Invocation Effet
uniq supprime les lignes identiques consécutives
uniq -u n’affiche que les lignes sans doublon adjacent
uniq -c préfixe chaque ligne par son nombre d’occurrences consécutives

uniq ne compare que des lignes voisines. Il faut donc trier avant lui, toujours dans cet ordre : sort | uniq, jamais uniq | sort.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "--- uniq sur une donnee NON triee : 11 groupes ---"
cut -d' ' -f9 "${WORK}/webserver.log" | uniq | wc -l
echo "--- le meme champ trie puis uniq : 4 codes distincts ---"
cut -d' ' -f9 "${WORK}/webserver.log" | sort | uniq
echo "--- agregation : occurrences de chaque code ---"
cut -d' ' -f9 "${WORK}/webserver.log" | sort | uniq -c

Quatorze lignes seulement, et déjà onze groupes non triés contre quatre codes : uniq sans sort ne compte rien, il ne fait que masquer les répétitions adjacentes. Le -c final est l’outil de statistique le plus simple du shell ; son compteur est précédé d’espaces dont le nombre dépend du système, raison pour laquelle on ne compare jamais une sortie de uniq -c « brute ».

AvertissementDeux ordres de grandeur de pièges
  1. grep casse les lignes : grep "404" trouve le motif n’importe où dans la ligne, y compris dans un chemin. La dernière ligne du journal, /rapport-404.pdf servie en 200, est comptée comme une erreur 404.
  2. sort mélange : uniq sur une entrée non triée produit un nombre de groupes sans sens.

Le premier se règle en extrayant le champ avant de filtrer, puis en ancrant le motif.

Traitement en streaming

Un pipeline ne charge pas le fichier en mémoire : chaque maillon lit des lignes, en produit, et les transmet. Deux conséquences pratiques.

D’abord, on peut plafonner la lecture sans connaître la taille du fichier. La cellule suivante ne lit que trois lignes :

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
head -n 3 "${WORK}/webserver.log" | cut -d' ' -f1,9

Ensuite, l’ordre des maillons a un coût réel. Placer le filtre qui élimine le plus de lignes le plus tôt possible réduit le travail de tous les maillons suivants :

correct :  cut -d' ' -f9 webserver.log | grep "^404$" | wc -l
coûteux :  cat webserver.log | cut -d' ' -f9,10 | sort | grep "^404 " | wc -l

Dans la version coûteuse, le sort trie la totalité du journal pour un compteur final. C’est exactement le genre de pipeline qui fait ramer un gros fichier de logs : ni mémoire, ni réseau, mais un tri inutile sur tout le contenu.

Exercice 1 — Premiers pipes

Consigne. En une commande par question, sans jamais écrire dans webserver.log :

  • la méthode de chaque ligne est le champ 6, mais elle est collée à une guillemet ouvrante. Listez, triées et dédoublonnées, les méthodes présentes ;
  • combien de lignes utilisent POST ?
  • pour chaque requête POST, affichez l’IP, le chemin et le code.

Tentez d’abord :

# 1. methodes utilisees, une par ligne, dans l'ordre alpha
#    attendues : GET puis POST
# 2. nombre de lignes POST
#    attendu : 2
# 3. lignes POST : IP, chemin, code
#    attendus : 10.0.0.2 /api/upload 500
#               203.0.113.4 /api/login 500
AstuceCorrigé

Le tr -d '"' retire les guillemets qui gênent la lecture du champ 6 ; sans lui, la sortie affiche "GET et "POST.

Solution
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "--- 1. methodes utilisees ---"
cut -d' ' -f6 "${WORK}/webserver.log" | tr -d '"' | sort -u
echo "--- 2. nombre de POST ---"
grep "POST" "${WORK}/webserver.log" | wc -l
echo "--- 3. detail des POST ---"
grep "POST" "${WORK}/webserver.log" | cut -d' ' -f1,7,9
--- 1. methodes utilisees ---
GET
POST
--- 2. nombre de POST ---
2
--- 3. detail des POST ---
10.0.0.2 /api/upload 500
203.0.113.4 /api/login 500

Toutes les requêtes POST du journal se terminent en 500 : le pipeline ne l’invente pas, il le rend visible.

Exercice 2 — Répartition des codes HTTP

Consigne. Construisez le pipeline, maillon par maillon :

  • la liste triée des codes présents, sans doublon ;
  • le nombre d’appels de chaque code ;
  • le même tableau, du code le plus fréquent au moins fréquent, avec une clé secondaire pour que l’ordre soit stable à compteur égal ;
  • le nombre exact de réponses 404, en refusant le piège du grep non borné.

Tentez d’abord :

# 1. codes distincts triés
#    attendus : 200 301 404 500
# 2. comptage par code
#    attendus : 200 x 7, 301 x 2, 404 x 3, 500 x 2
# 3. classement par frequence decroissante
#    premiere ligne attendue : 7 200
# 4. vraies reponses 404
#    attendu : 3
AstuceCorrigé

La question 4 est la plus importante : grep "404" répond 4, parce que le chemin /rapport-404.pdf porte le motif. En extrayant d’abord le champ 9 puis en ancrant avec ^404$, on retombe sur les 3 vraies erreurs.

Solution
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "--- 1. codes distincts ---"
cut -d' ' -f9 "${WORK}/webserver.log" | sort -u
echo "--- 2. comptage par code ---"
cut -d' ' -f9 "${WORK}/webserver.log" | sort | uniq -c
echo "--- 3. classement stable ---"
cut -d' ' -f9 "${WORK}/webserver.log" | sort | uniq -c | sort -k1,1nr -k2,2
echo "--- 4a. grep naive : 4 ---"
grep -c "404" "${WORK}/webserver.log"
echo "--- 4b. en champ ancre : 3 ---"
cut -d' ' -f9 "${WORK}/webserver.log" | grep -c "^404$"
--- 1. codes distincts ---
200
301
404
500
--- 2. comptage par code ---
      7 200
      2 301
      3 404
      2 500
--- 3. classement stable ---
      7 200
      3 404
      2 301
      2 500
--- 4a. grep naive : 4 ---
4
--- 4b. en champ ancre : 3 ---
3

Le comptage se lit ainsi : sept 200, trois 404, deux 301, deux 500, soit quatorze lignes. Le sort -k1,1nr -k2,2 final place 301 avant 500 à compteur égal : c’est la clé secondaire qui fixe cet ordre.

Exercice 3 — Clients et ressources

Consigne.

  • combien de clients distincts ont touché le serveur, et lesquels ;
  • le nombre de lignes par client, du plus actif au moins actif, ordre stable ;
  • les quatre ressources les plus demandées, avec leur compteur.

Tentez d’abord :

# 1. clients distincts
#    attendus : 10.0.0.2, 127.0.0.1, 192.168.0.5, 203.0.113.4
# 2. lignes par client, ordre stable
#    attendus : 4 pour 127.0.0.1, 4 pour 203.0.113.4, 3 pour 10.0.0.2, 3 pour 192.168.0.5
# 3. quatre premieres ressources par frequence
#    premiere attendue : 3 /index.html, seconde : 2 /about.html
AstuceCorrigé

Même patron pour les questions 2 et 3 : cut pour isoler la colonne, sort pour rendre les doublons adjacents, uniq -c pour compter, sort -k1,1nr -k2,2 pour classer avec un ordre déterministe.

Solution
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "--- 1. clients distincts ---"
cut -d' ' -f1 "${WORK}/webserver.log" | sort -u
echo "--- 2. lignes par client ---"
cut -d' ' -f1 "${WORK}/webserver.log" | sort | uniq -c | sort -k1,1nr -k2,2
echo "--- 3. ressources les plus demandees ---"
cut -d' ' -f7 "${WORK}/webserver.log" | sort | uniq -c | sort -k1,1nr -k2,2 | head -n 4
echo "--- 4. nombre de ressources distinctes ---"
cut -d' ' -f7 "${WORK}/webserver.log" | sort -u | wc -l
--- 1. clients distincts ---
10.0.0.2
127.0.0.1
192.168.0.5
203.0.113.4
--- 2. lignes par client ---
      4 127.0.0.1
      4 203.0.113.4
      3 10.0.0.2
      3 192.168.0.5
--- 3. ressources les plus demandees ---
      3 /index.html
      2 /about.html
      1 /api/login
      1 /api/upload
--- 4. nombre de ressources distinctes ---
11

Quatre clients, onze ressources distinctes : le /rapport-404.pdf tout à l’heure est bien une ressource servie, pas une erreur.

Exercice 4 — Synthèse automatique

Consigne. Enchaînez les acquis en un petit rapport, sans éditeur : un fichier rapport.txt construit par une première redirection > puis sept ajouts >>, de sorte que sa relecture affiche la réponse à la question 2 (les lignes par client) mélangée aux compteurs globaux. La question 3 (les ressources les plus demandées) reste hors du rapport.

Solution
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "=== total de lignes ===" > "${WORK}/rapport.txt"
wc -l < "${WORK}/webserver.log" >> "${WORK}/rapport.txt"
echo "=== codes distincts ===" >> "${WORK}/rapport.txt"
cut -d' ' -f9 "${WORK}/webserver.log" | sort | uniq -c | sort -k1,1nr -k2,2 >> "${WORK}/rapport.txt"
echo "=== lignes par client ===" >> "${WORK}/rapport.txt"
cut -d' ' -f1 "${WORK}/webserver.log" | sort | uniq -c | sort -k1,1nr -k2,2 >> "${WORK}/rapport.txt"
echo "=== reponses 404 ===" >> "${WORK}/rapport.txt"
cut -d' ' -f9 "${WORK}/webserver.log" | grep -c "^404$" >> "${WORK}/rapport.txt"
cat "${WORK}/rapport.txt"
=== total de lignes ===
14
=== codes distincts ===
      7 200
      3 404
      2 301
      2 500
=== lignes par client ===
      4 127.0.0.1
      4 203.0.113.4
      3 10.0.0.2
      3 192.168.0.5
=== reponses 404 ===
3

Le rapport compte 14 lignes : quatre en-têtes et dix lignes de données — un total, quatre compteurs de codes, quatre compteurs de clients et un compte 404. Vérifiez avec wc -l < rapport.txt. Le fait que ce nombre égale celui du journal est une coïncidence de ce fixture, pas une loi. C’est un contrôle à faire systématique sur tout fichier produit par des >> : une seule ligne de plus ou de moins signale un > oublié en tête, qui aurait écrasé le début du rapport.

AvertissementUn seul >, des >> ensuite

> écrase la destination, >> ajoute. Repasser une cellule de synthèse qui commence par > ne casse rien (le rapport est régénéré), mais un > placé au milieu du fichier ampute le rapport de tout ce qui le précédait. En cas de doute, relisez le fichier avec wc -l et cat.

Point de contrôle

Exécutez cette cellule seule : elle confirme que le fichier est intact et que vous êtes toujours dans le répertoire de la fiche.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
pwd
wc -l "${WORK}/webserver.log" "${WORK}/rapport.txt"

Auto-vérification

Les treize valeurs attendues ci-dessous ont été recalculées à partir du même fichier, avec les commandes de la fiche : chaque ligne OK est une preuve, pas une affirmation.

cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
LOG="${WORK}/webserver.log"
REPS="${WORK}/rapport.txt"
total=0
ok=0
check() {
  total=$((total + 1))
  if [ "$2" = "$3" ]; then
    ok=$((ok + 1))
    echo "OK   $1 : $2"
  else
    echo "KO   $1 : obtenu [$2], attendu [$3]"
  fi
}
check "lignes du journal" "$(wc -l < "${LOG}" | tr -d ' ')" "14"
check "codes distincts" "$(cut -d' ' -f9 "${LOG}" | sort -u | paste -sd' ' -)" "200 301 404 500"
check "grep naive 404 (piege)" "$(grep -c "404" "${LOG}")" "4"
check "vraies reponses 404" "$(cut -d' ' -f9 "${LOG}" | grep -c "^404$")" "3"
check "reponses 200" "$(cut -d' ' -f9 "${LOG}" | grep -c "^200$")" "7"
check "code le plus frequent" "$(cut -d' ' -f9 "${LOG}" | sort | uniq -c | sort -k1,1nr -k2,2 | head -n 1 | tr -s ' ' | sed 's/^ //')" "7 200"
check "requetes POST" "$(grep -c "POST" "${LOG}")" "2"
check "clients distincts" "$(cut -d' ' -f1 "${LOG}" | sort -u | wc -l | tr -d ' ')" "4"
check "client le plus actif" "$(cut -d' ' -f1 "${LOG}" | sort | uniq -c | sort -k1,1nr -k2,2 | head -n 1 | tr -s ' ' | sed 's/^ //')" "4 127.0.0.1"
check "ressources distinctes" "$(cut -d' ' -f7 "${LOG}" | sort -u | wc -l | tr -d ' ')" "11"
check "uniq sans tri prealable" "$(cut -d' ' -f9 "${LOG}" | uniq | wc -l | tr -d ' ')" "11"
check "tailles triees numeriquement" "$(cut -d' ' -f10 "${LOG}" | sort -n -u | head -n 3 | paste -sd' ' -)" "64 128 256"
check "lignes du rapport" "$(wc -l < "${REPS}" | tr -d ' ')" "14"
echo "OK : ${ok} / ${total}"

Treize OK attendus, et la dernière ligne le dit. Trois contrôles méritent une explication. grep naive 404 (piege) vaut 4 et non 3 : c’est bien le piège, il est vérifié comme les autres. uniq sans tri préalable vaut 11 quand sort -u sur le même champ vaut 4 : les deux sont vrais, mais un seul compte des codes. lignes du rapport 14 est la preuve que les redirections > et >> de l’exercice 4 se sont enchaînées dans le bon ordre.

À retenir

  • Le pipe | branche la sortie standard de gauche sur l’entrée standard de droite.
  • Un pipeline se lit de gauche à droite et s’allonge par la droite.
  • cat fichier | grep X = grep X fichier : le cat initial est presque toujours inutile.
  • Compter des lignes filtrées : wc -l placé en dernier maillon.
  • grep -c compte déjà : grep -c "POST" journal remplace grep "POST" journal | wc -l.
  • Isoler une colonne avant de compter : cut -d' ' -f9 (cf. tableau des champs plus haut).
  • sort trie du texte par défaut : un comptage numérique exige sort -n.
  • uniq ne voit que les doublons adjacents : toujours sort | uniq, jamais l’inverse.
  • Agréger et classer : sort | uniq -c | sort -k1,1nr -k2,2, avec une clé secondaire stable.
  • Un pipeline est un streaming : le fichier n’est jamais chargé en entier, et un filtre posé tôt allège tous les maillons suivants.
  • > écrase, >> ajoute.

Trois réflexes : extraire le champ concerné avant de filtrer, pour ne pas compter un motif apparu dans un chemin ; toujours trier avant uniq ; vérifier le nombre de lignes de toute sortie de pipeline avec wc -l, la sienne comme celle du fichier qu’on lui donne.

La fiche 3 fournit les outils de découpe et de transformation qu’ici l’on enchaîne. La fiche 5 met ces pipelines dans un script, pour les rejouer et les paramétrer.

Réutilisation