Di recente, ho iniziato a lavorare con Asciidoctor.js e su il progetto Asciidoctor.js-pug e Asciidoctor-templates.js.
Non è sempre facile essere immediatamente efficaci quando si scava per la prima volta in una base di codice contenente diverse migliaia di righe. Ma la mia arma segreta per trovare la mia strada attraverso così tante righe di codice è il grep
strumento.
Condividerò con te come usare il comando grep in Linux con esempi.
Esempi utili nella vita reale dei comandi grep in Linux
Se guardi dentro man
, vedrai quella breve descrizione per grep
strumento:"stampa linee che corrispondono a un motivo".
Tuttavia, non lasciarti ingannare da una definizione così umile:grep
è uno degli strumenti più utili nella casella degli strumenti Unix e ci sono innumerevoli occasioni per usarlo non appena lavori con file di testo.
È sempre meglio avere esempi del mondo reale per imparare come funzionano le cose. Quindi, userò l'albero dei sorgenti di Asciidoctor.js per illustrare alcuni dei grep
capacità.
Puoi scaricare quell'albero dei sorgenti da GitHub e, se lo desideri, puoi anche controllare lo stesso set di modifiche che ho usato quando ho scritto questo articolo. Ciò ti assicurerà di ottenere risultati perfettamente identici a quelli descritti nel resto di questo articolo:
git clone https://github.com/asciidoctor/asciidoctor.js
cd asciidoctor.js
git checkout v1.5.6-rc.1
1. Trova tutte le occorrenze di una stringa (utilizzo di base)
Asciidoctor.js supporta il motore JavaScript Nashorn per la piattaforma Java. Non conosco Nashorn, quindi potrei cogliere l'occasione per saperne di più esplorando le parti del progetto che fanno riferimento a quel motore JavaScript.
Come punto di partenza, ho verificato se c'erano alcune impostazioni relative a Nashorn nel package.json
file che descrive le dipendenze del progetto:
[email protected]:~$ grep nashorn package.json
"test": "node npm/test/builder.js && node npm/test/unsupported-features.js && node npm/test/jasmine-browser.js && node npm/test/jasmine-browser-min.js && node npm/test/jasmine-node.js && node npm/test/jasmine-webpack.js && npm run test:karmaBrowserify && npm run test:karmaRequirejs && node npm/test/nashorn.js",
Sì, a quanto pare c'erano alcuni test specifici di Nashorn. Quindi, indaghiamo un po' di più.
2. Ricerca senza distinzione tra maiuscole e minuscole in un set di file
Ora, voglio dare un'occhiata più da vicino ai file da ./npm/test/
directory che menziona esplicitamente Nashorn.
Una ricerca senza distinzione tra maiuscole e minuscole (-i
opzione) è probabilmente migliore qui poiché ho bisogno di trovare entrambi i riferimenti a nashorn
e Nashorn
(o qualsiasi altra combinazione di caratteri maiuscoli e minuscoli):
[email protected]:~$ grep -i nashorn npm/test/*.js
npm/test/nashorn.js:const nashornModule = require('../module/nashorn');
npm/test/nashorn.js:log.task('Nashorn');
npm/test/nashorn.js:nashornModule.nashornRun('jdk1.8.0');
In effetti, la distinzione tra maiuscole e minuscole è stata utile qui. Altrimenti, mi sarei perso il require('../module/nashorn')
dichiarazione. Senza dubbio dovrei esaminare quel file in modo più dettagliato in seguito.
3. Trova tutti i file non corrispondenti
A proposito, ci sono alcuni file non specifici di Nashorm nel npm/test/
directory? Per rispondere a questa domanda, possiamo usare l'opzione "stampa file non corrispondenti" di grep (-L
opzione):
sh$ grep -iL nashorn npm/test/*
npm/test/builder.js
npm/test/jasmine-browser-min.js
npm/test/jasmine-browser.js
npm/test/jasmine-node.js
npm/test/jasmine-webpack.js
npm/test/unsupported-features.js
Nota come con -L
opzione l'output di grep
è cambiato per visualizzare solo i nomi dei file. Quindi, nessuno dei file sopra contiene la stringa "nashorn" (indipendentemente dal caso). Ciò non significa che non siano in qualche modo correlati a quella tecnologia, ma almeno le lettere "n-a-s-h-o-r-n" non sono presenti.
4. Trovare modelli in file nascosti e ricorsivamente in sottodirectory
Gli ultimi due comandi utilizzavano un pattern shell glob per passare l'elenco dei file da esaminare a grep
comando.
Tuttavia, questo ha alcune limitazioni intrinseche:la stella (*
) non corrisponderà ai file nascosti. Né corrisponderà ai file (eventualmente) contenuti nelle sottodirectory.
Una soluzione sarebbe combinare grep
con il comando find invece di fare affidamento su un pattern di shell glob:
# This is not efficient as it will spawn a new grep process for each file
[email protected]:~$ find npm/test/ -type f -exec grep -iL nashorn \{} \;
# This may have issues with filenames containing space-like characters
[email protected]:~$ grep -iL nashorn $(find npm/test/ -type f)
Come l'ho menzionato commentando il blocco di codice sopra, ognuna di queste soluzioni presenta degli svantaggi.
Per quanto riguarda i nomi di file contenenti caratteri simili a spazi, ti lascio esaminare il grep -z
opzione che, combinata con il -print0
opzione del find
comando, può attenuare il problema. Non esitare a utilizzare la sezione commenti alla fine di questo articolo per condividere le tue idee su quell'argomento!
Tuttavia, una soluzione migliore potrebbe utilizzare il "ricorsivo" (-r
) opzione di grep
. Con questa opzione, fornisci sulla riga di comando la radice del tuo albero di ricerca (la directory iniziale) invece dell'elenco esplicito di nomi di file da esaminare.
Con il -r
opzione, grep cercherà tutti i file nella directory specificata, inclusi quelli nascosti, e quindi scenderà ricorsivamente in qualsiasi sottodirectory:
[email protected]:~$ grep -irL nashorn npm/test/npm/
npm/test/builder.js
npm/test/jasmine-browser-min.js
npm/test/jasmine-browser.js
npm/test/jasmine-node.js
npm/test/jasmine-webpack.js
npm/test/unsupported-features.js
In realtà, con quell'opzione, potrei anche iniziare la mia esplorazione un livello sopra per vedere che ci sono test non npm che prendono di mira anche Nashorn:
[email protected]:~$ grep -irL nashorn npm/
Ti ho lasciato testare da solo quel comando per vederne l'esito; ma come suggerimento, posso dire che dovresti trovare molti più file corrispondenti!
5. Filtraggio dei file per nome (usando espressioni regolari)
Quindi, sembra che ci siano alcuni test specifici di Nashorn in quel progetto. Poiché Nashorn è Java, un'altra domanda che potrebbe essere sollevata sarebbe "ci sono alcuni file sorgente Java nel progetto che menzionano esplicitamente Nashorn?" .
A seconda della versione di grep
che usi, ci sono almeno due soluzioni per rispondere a questa domanda.
Il primo è usare grep
per trovare tutti i file contenenti il pattern "nashorn", quindi reindirizzare l'output di quel primo comando a un secondo grep
istanza che filtra i file di origine non Java:
[email protected]:~$ grep -ir nashorn ./ | grep "^[^:]*\.java"
./spec/nashorn/AsciidoctorConvertWithNashorn.java:public class AsciidoctorConvertWithNashorn {
./spec/nashorn/AsciidoctorConvertWithNashorn.java: ScriptEngine engine = engineManager.getEngineByName("nashorn");
./spec/nashorn/AsciidoctorConvertWithNashorn.java: engine.eval(new FileReader("./spec/nashorn/asciidoctor-convert.js"));
./spec/nashorn/BasicJavascriptWithNashorn.java:public class BasicJavascriptWithNashorn {
./spec/nashorn/BasicJavascriptWithNashorn.java: ScriptEngine engine = engineManager.getEngineByName("nashorn");
./spec/nashorn/BasicJavascriptWithNashorn.java: engine.eval(new FileReader("./spec/nashorn/basic.js"));
La prima metà del comando dovrebbe essere ormai comprensibile. Ma che dire di quella parte “^[\^:]*\\.java”?
A meno che non specifichi il -F
opzione, grep
presuppone che il modello di ricerca sia un'espressione regolare. Ciò significa che, oltre ai caratteri semplici che corrisponderanno alla lettera, hai accesso a una serie di metacaratteri per descrivere schemi più complessi. Il modello che ho usato sopra corrisponderà solo a:
^
l'inizio della riga[^:]*
seguito da una sequenza di qualsiasi carattere tranne i due punti\.
seguito da un punto (il punto ha un significato speciale in regex , quindi ho dovuto proteggerlo con una barra rovesciata per esprimere che voglio una corrispondenza letterale)java
e seguito dalle quattro lettere "java".
In pratica, poiché grep
utilizzerà i due punti per separare il nome del file dal contesto, conservo solo le righe con .java
nella sezione nome file. Vale la pena menzionarlo sarebbe trova anche .javascript
nomi di file. Questo è qualcosa che ho lasciato provare a risolvere da solo, se lo desideri.
6. Filtrare i file in base al nome usando grep
Le espressioni regolari sono estremamente potenti. Tuttavia, in quel caso particolare, sembra eccessivo. Senza menzionare la soluzione di cui sopra, passiamo del tempo a esaminare tutti i file alla ricerca del modello "nashorn":la maggior parte dei risultati viene eliminata dal secondo passaggio della pipeline.
Se stai usando la versione GNU di grep
, qualcosa che è probabile se stai usando Linux, hai un'altra soluzione anche se con --include
opzione. Questo indica grep
per cercare solo nei file il cui nome corrisponde al pattern glob specificato:
[email protected]:~$ grep -ir nashorn ./ --include='*.java'
./spec/nashorn/AsciidoctorConvertWithNashorn.java:public class AsciidoctorConvertWithNashorn {
./spec/nashorn/AsciidoctorConvertWithNashorn.java: ScriptEngine engine = engineManager.getEngineByName("nashorn");
./spec/nashorn/AsciidoctorConvertWithNashorn.java: engine.eval(new FileReader("./spec/nashorn/asciidoctor-convert.js"));
./spec/nashorn/BasicJavascriptWithNashorn.java:public class BasicJavascriptWithNashorn {
./spec/nashorn/BasicJavascriptWithNashorn.java: ScriptEngine engine = engineManager.getEngineByName("nashorn");
./spec/nashorn/BasicJavascriptWithNashorn.java: engine.eval(new FileReader("./spec/nashorn/basic.js"));
7. Trovare le parole
La cosa interessante del progetto Asciidoctor.js è che è un progetto multilingue. Al suo interno, Asciidoctor è scritto in Ruby, quindi, per essere utilizzabile nel mondo JavaScript, deve essere "traspilato" usando Opal, un compilatore da sorgente a sorgente da Ruby a JavaScript. Un'altra tecnologia che non conoscevo prima.
Quindi, dopo aver esaminato le specificità del Nashorn, mi sono affidato il compito di comprendere meglio l'API Opal. Come primo passo in quella ricerca, ho cercato tutte le menzioni di Opal
oggetto globale nei file JavaScript del progetto. Potrebbe apparire con affettazioni (Opal =
), accesso membri (Opal.
) o forse anche in altri contesti. Un'espressione regolare farebbe il trucco. Tuttavia, ancora una volta, grep
ha una soluzione più leggera per risolvere quel caso d'uso comune. Usando il -w
opzione, corrisponderà solo a parole , ovvero schemi preceduti e seguiti da un carattere non di parola. Un carattere diverso da una parola è l'inizio della riga, la fine della riga o qualsiasi carattere che non sia una lettera, né una cifra, né un trattino basso:
[email protected]:~$ grep -irw --include='*.js' Opal .
...
8. colorando l'output
Non ho copiato l'output del comando precedente poiché ci sono molte corrispondenze. Quando l'output è così denso, potresti voler aggiungere un po' di colore per facilitare la comprensione. Se questo non è già configurato per impostazione predefinita sul tuo sistema, puoi attivare quella funzione usando GNU --color
opzione:
[email protected]:~$ grep -irw --color=auto --include='*.js' Opal .
...
Dovresti ottenere lo stesso risultato lungo di prima, ma questa volta la stringa di ricerca dovrebbe apparire a colori se non era già il caso.
9. Conteggio delle righe corrispondenti o dei file corrispondenti
Ho menzionato due volte che l'output dei comandi precedenti era molto lungo. Quanto tempo esattamente?
[email protected]:~$ grep -irw --include='*.js' Opal . | wc -l
86
Ciò significa che abbiamo un totale 86 righe corrispondenti in tutte i fascicoli esaminati. Tuttavia, quanti file diversi corrispondono? Con il -l
opzione puoi limitare il grep
genera i file corrispondenti invece di visualizzare le linee corrispondenti . Quindi quella semplice modifica indicherà quanti file corrispondono:
[email protected]:~$ grep -irwl --include='*.js' Opal . | wc -l
20
Se questo ti ricorda il -L
opzione, nessuna sorpresa:poiché è relativamente comune, minuscole/maiuscole vengono utilizzate per distinguere le opzioni complementari. -l
visualizza i nomi di file corrispondenti. -L
visualizza nomi di file non corrispondenti. Per un altro esempio, ti lascio controllare il manuale per il -h
/-H
opzioni.
Chiudiamo quella parentesi e torniamo ai nostri risultati:86 righe corrispondenti. 20 file corrispondenti. Tuttavia, come vengono distribuite le linee corrispondenti nei file corrispondenti ? Possiamo saperlo usando il -c
opzione di grep
che conterà il numero di righe corrispondenti per file esaminato (compresi i file con zero corrispondenze):
[email protected]:~$ grep -irwc --include='*.js' Opal .
...
Spesso, quell'output necessita di una post-elaborazione poiché mostra i suoi risultati nell'ordine in cui i file sono stati esaminati e include anche file senza alcuna corrispondenza, cosa che di solito non ci interessa. Quest'ultimo è abbastanza facile da risolvere:
[email protected]:~$ grep -irwc --include='*.js' Opal . | grep -v ':0$'
Per quanto riguarda l'ordine, puoi aggiungere il comando sort alla fine della pipeline:
[email protected]:~$ grep -irwc --include='*.js' Opal . | grep -v ':0$' | sort -t: -k2n
Ti lascio controllare il sort
manuale dei comandi per il significato esatto delle opzioni che ho usato. Non dimenticare di condividere i tuoi risultati utilizzando la sezione commenti qui sotto!
10. Trovare la differenza tra due set corrispondenti
Se ricordi, qualche comando fa ho cercato la parola "Opale." Tuttavia, se cerco nello stesso set di file per tutte le occorrenze della stringa “Opale”, ottengo una ventina di risposte in più:
[email protected]:~$ grep -irw --include='*.js' Opal . | wc -l
86
[email protected]:~$ grep -ir --include='*.js' Opal . | wc -l
105
Sarebbe interessante trovare la differenza tra questi due set. Quindi, quali sono le righe che contengono le quattro lettere "opale" di seguito, ma dove quelle quattro lettere non formano un'intera parola?
Non è così facile rispondere a questa domanda. Perché lo uguale la riga può contenere entrambi la parola Opal così come una parola più grande contenente quelle quattro lettere. Ma come prima approssimazione, puoi usare quella pipeline:
[email protected]:~$ grep -ir --include='*.js' Opal . | grep -ivw Opal
./npm/examples.js: const opalBuilder = OpalBuilder.create();
./npm/examples.js: opalBuilder.appendPaths('build/asciidoctor/lib');
./npm/examples.js: opalBuilder.appendPaths('lib');
...
Apparentemente, la mia prossima tappa sarebbe indagare su opalBuilder
oggetto, ma sarà per un altro giorno.
L'ultima parola
Ovviamente, non capirai l'organizzazione di un progetto, tanto meno l'architettura del codice, semplicemente emettendo un paio di grep
comandi!
Tuttavia, trovo quel comando inevitabile per identificare benchmark e punti di partenza quando si esplora una nuova base di codice.
Quindi, spero che questo articolo ti abbia aiutato a capire la potenza di grep
comando e che lo aggiungerai alla tua cassetta degli attrezzi. Senza dubbio non te ne pentirai!