$ grep — corso
Lezione 06 di 10

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.

Teoria

After, Before, Context

Tre flag, una sola idea: mostrare alcune righe intorno a ogni riga che matcha.

flagsignificato
-A Nafter: N righe dopo ogni match
-B Nbefore: N righe prima di ogni match
-C Ncontext: 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.

Pratica

Debug di un log applicativo

Lavoriamo su un log realistico. Cerchiamo le righe di errore senza contesto e vediamo cosa otteniamo:

app.log — senza contesto
$ 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:

2 righe prima, 2 righe dopo
$ 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:

match multipli, ognuno col suo contesto
$ 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)
Trucco

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:

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.

Combinato con -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.