Ricerca ricorsiva nei progetti
Con un solo flag, -r, grep diventa un motore di ricerca a tutti gli
effetti sopra un albero di file. È il modo per usarlo dentro repository di codice,
cartelle di log, archivi di documenti — purché si sappia escludere il
rumore.
-r, -R, e i filtri di percorso
Con -r grep scende ricorsivamente nelle sottocartelle. Se non gli
passi un percorso, parte da . (la directory corrente). C'è anche
-R: differisce da -r solo per il fatto che segue i
link simbolici. Nel dubbio si usa -r: è più sicuro perché non
rischia di entrare in loop su link circolari.
I flag che fanno la differenza nell'uso reale sono i filtri di percorso:
| flag | cosa fa |
|---|---|
| --include=GLOB | cerca solo nei file il cui nome matcha il glob |
| --exclude=GLOB | salta i file il cui nome matcha il glob |
| --exclude-dir=NAME | salta intere directory che si chiamano così |
Senza queste opzioni una ricerca ricorsiva in un progetto JavaScript apre
node_modules, in un progetto Python apre __pycache__,
in un repository .git e ti restituisce un fiume di rumore in cui non
trovi nulla. Queste opzioni sono la chiave di una grep ricorsiva
utilizzabile.
-r e --include sono
supportati, ma --exclude-dir richiede una versione recente. Se non
funziona, installa ggrep (visto nella lezione 1).
Cercare dentro un progetto
Immaginiamo un classico progetto web. Vogliamo trovare in quale file è definita
la funzione calculatePrice:
$ grep -rn "calculatePrice" . ./src/checkout.js:42:function calculatePrice(items) { ./src/checkout.js:78: total = calculatePrice(cart); ./node_modules/lib-bigone/src/util.js:1234: // see calculatePrice ./node_modules/lib-bigone/test/cart.test.js:88: it("calculatePrice") ... centinaia di altre righe dentro node_modules ...
Disastro: tutto ciò che ci interessa è annegato. Aggiungiamo le esclusioni:
$ grep -rn "calculatePrice" . \ --include="*.js" \ --exclude-dir=node_modules \ --exclude-dir=.git \ --exclude-dir=dist ./src/checkout.js:42:function calculatePrice(items) { ./src/checkout.js:78: total = calculatePrice(cart); ./src/api/orders.js:14:const price = calculatePrice(order.items);
Tre risultati, tutti pertinenti. --exclude-dir può essere ripetuto
tante volte quante servono. --include accetta i normali wildcard
della shell (*.js, *.{js,ts} non sempre — meglio
ripetere --include due volte).
Caso opposto: cerca un testo dentro qualsiasi tipo di file ma evita le
cartelle di build e i file binari (grep lo fa di default, mostrando
Binary file ... matches):
$ grep -rnI "API_KEY" . --exclude-dir={.git,node_modules,dist,build} ./.env.example:3:API_KEY=your-key-here ./README.md:42:Setta la variabile API_KEY prima di partire. ./src/config.js:8:const key = process.env.API_KEY;
Il flag -I (i maiuscola) salta i file binari. L'espansione
{a,b,c} è una comodità della shell che permette di scrivere
--exclude-dir una sola volta.
Crea il tuo alias grepcode e non pensarci più
Le esclusioni sono sempre le stesse. Invece di scriverle ogni volta, mettiamole una volta sola in una funzione di shell e dimentichiamocele:
# grep nel codice, saltando build/cache/git/vendor grepcode() { grep -rnI "$@" . \ --exclude-dir=.git \ --exclude-dir=node_modules \ --exclude-dir=.next \ --exclude-dir=dist \ --exclude-dir=build \ --exclude-dir=vendor \ --exclude-dir=__pycache__ \ --exclude-dir=.venv \ --exclude="*.min.js" \ --exclude="*.lock" }
Ora grepcode "calculatePrice" fa il giusto, e tutti gli argomenti di
grep continuano a funzionare: grepcode -i "todo", grepcode -E
"id|uuid", ecc. È una funzione e non un alias proprio perché vogliamo
passare argomenti arbitrari.
grepfn: per cercare una definizione
di funzione invece che ogni occorrenza, basta cambiare il pattern in arrivo:
grepfn() { grepcode -E "function $1|def $1|const $1\s*=|$1\s*\("; }.
Lo personalizzi col tuo linguaggio.
Combinato con -l (visto nella lezione 2) diventa il modo più rapido
per aprire nell'editor tutti i file che contengono qualcosa:
$ vim $(grepcode -l "TODO") # oppure con VS Code $ code $(grepcode -l "TODO")