Vedere le righe attorno al match
Una riga di log, da sola, raramente racconta tutta la storia. Le opzioni
-A, -B e -C aggiungono contesto al
match: senza di loro grep è una lente troppo stretta per leggere log e
configurazioni.
After, Before, Context
Tre flag, una sola idea: mostrare alcune righe intorno a ogni riga che matcha.
| flag | significato |
|---|---|
| -A N | after: N righe dopo ogni match |
| -B N | before: N righe prima di ogni match |
| -C N | context: N righe sia prima sia dopo |
Tra un blocco di contesto e l'altro grep stampa una riga di separazione
--. Si può sopprimere con --no-group-separator, oppure
cambiare con --group-separator=STRING (solo GNU grep).
Per quale problema esistono queste opzioni? Per uno semplice: il match è il punto interessante, ma raramente è il punto causale. L'errore lo cerchi, ma è la riga prima o dopo a dirti perché è successo.
Debug di un log applicativo
Lavoriamo su un log realistico. Cerchiamo le righe di errore senza contesto e vediamo cosa otteniamo:
$ cat > app.log <<'EOF' 10:00:01 [INFO] utente=alice login ok 10:00:02 [INFO] utente=alice query: SELECT * FROM users 10:00:02 [INFO] utente=alice query ok (12ms) 10:00:08 [INFO] utente=bob login ok 10:00:09 [INFO] utente=bob query: SELECT * FROM orders WHERE id=42 10:00:09 [WARN] utente=bob query lenta (1830ms) 10:00:09 [ERROR] utente=bob timeout connessione db 10:00:10 [INFO] retry tra 5s 10:00:15 [INFO] utente=bob riconnessione ok EOF $ grep ERROR app.log 10:00:09 [ERROR] utente=bob timeout connessione db
Abbiamo un errore, ma non sappiamo cosa è successo prima né come è andata a finire. Aggiungiamo contesto:
$ grep -B 2 -A 2 ERROR app.log 10:00:09 [INFO] utente=bob query: SELECT * FROM orders WHERE id=42 10:00:09 [WARN] utente=bob query lenta (1830ms) 10:00:09 [ERROR] utente=bob timeout connessione db 10:00:10 [INFO] retry tra 5s 10:00:15 [INFO] utente=bob riconnessione ok
Ora la storia è completa: c'era una query lenta, è andata in timeout, l'app ha
riprovato, ha avuto successo. La stessa cosa scritta più compatta con
-C 2 (cioè -A 2 -B 2 insieme).
Su file di log con tantissimi match, la riga separatrice -- aiuta
a distinguere ogni blocco:
$ grep -C 1 WARN app.log 10:00:09 [INFO] utente=bob query: SELECT * FROM orders WHERE id=42 10:00:09 [WARN] utente=bob query lenta (1830ms) 10:00:09 [ERROR] utente=bob timeout connessione db -- # (se ci fosse un altro warning, qui apparirebbe con il suo contesto)
Solo "after": estrarre un blocco da una configurazione
Il flag -A da solo è straordinario per leggere file di configurazione
a blocchi: il pattern individua l'etichetta del blocco, e
-A N mostra le righe di valore che seguono. Esempio classico, un
docker-compose.yml:
$ cat docker-compose.yml services: web: image: nginx:latest ports: - "80:80" volumes: - ./html:/usr/share/nginx/html db: image: postgres:15 environment: POSTGRES_USER: admin POSTGRES_PASSWORD: secret volumes: - db_data:/var/lib/postgresql/data # mostra solo il blocco del servizio db $ grep -A 6 "^ db:" docker-compose.yml db: image: postgres:15 environment: POSTGRES_USER: admin POSTGRES_PASSWORD: secret volumes: - db_data:/var/lib/postgresql/data
Lo stesso ragionamento vale per qualunque file strutturato a sezioni: file
.ini, regole di nginx, systemd units,
sshd_config, blocchi <VirtualHost> di Apache.
-r e -l nella lezione
precedente: "in quale file di configurazione c'è il blocco che inizia con
listen 443?" diventa
grep -rl "listen 443" /etc/nginx, e poi
grep -A 20 "listen 443" /etc/nginx/sites-enabled/quel-file per
vedere tutto il blocco. Due comandi, problema risolto.
Suggerimento finale: se le righe del blocco hanno un'indentazione costante
(come YAML), puoi anche fermarti dove l'indentazione cambia usando
awk, ma per un colpo d'occhio rapido grep -A è già
la risposta giusta nove volte su dieci.