$ grep — corso
Lezione 09 di 10

Pipe e composizione

Grep dà il meglio di sé quando non legge un file: quando sta in mezzo a una pipe, a filtrare l'output di un altro comando. È in questa posizione che passa da utility a tassello del modo di pensare Unix.

Teoria

La filosofia: piccoli comandi, in serie

Il principio dietro alla shell Unix è che ogni comando faccia una cosa, e che il modo di combinarli sia mettere l'output dell'uno in input all'altro attraverso il simbolo |. In questa visione, grep è il filtro per eccellenza: legge righe da standard input, ne lascia passare alcune, scarta le altre.

Quando grep non riceve un file come argomento, si mette automaticamente in ascolto dello standard input. Da lì si crea il pattern che si ripete in mille varianti:

il pattern di base
COMANDO | grep PATTERN | ALTRO_COMANDO

Da questa idea derivano usi che vedremo qui sotto: filtrare processi in esecuzione, monitorare log in tempo reale, cercare nei commit di git, contare occorrenze, eccetera. Sono casi diversi in superficie, ma sotto è sempre lo stesso schema.

Pratica

Sette pipe che userai ovunque

1. Trovare un processo per nome:

ps + grep
$ ps aux | grep nginx
root  1234  0.0 0.1  nginx: master process
www   1235  0.0 0.1  nginx: worker process
tu    9999  0.0 0.0  grep --color=auto nginx

Nota la riga finale: grep ha trovato anche se stesso. Il fix è il trucco di questa lezione.

2. Filtrare l'output di ls:

ls + grep
$ ls -la | grep '^d'         # solo directory
$ ls -la /etc | grep '\.conf$'  # file .conf in /etc

3. Cercare in git log:

git log + grep
$ git log --oneline | grep -i 'fix'
a1b2c3d fix: corretto bug login
e4f5g6h Fix: typo nel readme

# commit di un certo autore che riguardano una zona
$ git log --author=Anna --pretty=%s | grep -i checkout

4. Monitorare un log in tempo reale:

tail -f + grep
$ tail -f /var/log/syslog | grep --line-buffered -i error

--line-buffered è importante: senza, grep accumula l'output in un buffer e tu non vedi le righe in tempo reale. Con, ogni riga matchata appare appena viene letta.

5. Trovare e operare sui file (con find):

find + xargs + grep
$ find . -name '*.py' -print0 | xargs -0 grep -l 'import requests'

6. Contare in modo composto:

contare visitatori unici
$ grep '"GET ' access.log | awk '{print $1}' | sort -u | wc -l
347

7. Pattern multipli da file con -f:

-f patterns.txt
$ cat > ip-da-bannare.txt <<'EOF'
192.0.2.5
198.51.100.12
203.0.113.8
EOF

# cerca nel log tutte le righe che contengono uno qualunque di quegli IP
$ grep -f ip-da-bannare.txt access.log

-f è oro: invece di mettere decine di alternative | nella riga di comando, tieni i pattern in un file e li passi così. Combinato con -F (stringhe letterali, niente regex) diventa anche molto veloce su file enormi.

Trucco

Il classico [n]ginx: evitare di trovare se stesso

Avevamo visto nella lezione 2 il problema: quando filtri l'output di ps per trovare un processo, l'ultima riga del risultato è il grep stesso, che ovviamente contiene la stringa che cercavi. Le soluzioni che girano sono tre, in ordine crescente di eleganza:

Soluzione mediocre: aggiungere grep -v grep in coda.

funziona, ma rumorosa
$ ps aux | grep nginx | grep -v grep
root  1234  0.0 0.1  nginx: master process
www   1235  0.0 0.1  nginx: worker process

Soluzione elegante: il pattern [n]ginx.

il trucco delle parentesi quadre
$ ps aux | grep '[n]ginx'
root  1234  0.0 0.1  nginx: master process
www   1235  0.0 0.1  nginx: worker process

Il pattern [n]ginx matcha esattamente la stringa nginx (perché [n] è una classe di un solo carattere: la lettera n). Ma la riga del comando grep stesso, vista da ps, contiene letteralmente la stringa [n]ginx — che non è uguale a nginx. Il grep, cercando nginx, non trova se stesso. È un piccolo capolavoro di logica.

Soluzione moderna: usa pgrep e basta.

pgrep, pensato apposta
$ pgrep -af nginx
1234 nginx: master process
1235 nginx: worker process
In conclusione: nei tuoi script usa pgrep se è disponibile (lo è su tutti i sistemi moderni), perché è chiaro e non ha il problema. Ma conoscere il trucco [n]ginx è obbligatorio: lo trovi negli script di tutto il mondo, e ti capiterà di doverlo leggere, mantenere, o spiegare a qualcun altro.