Cerca ricorsivamente uno schema / testo solo nel nome file specificato di una directory?


16

Ho una directory (ad esempio, abc/def/efg) con molte sottodirectory (ad esempio ,:) abc/def/efg/(1..300). Tutte queste sottodirectory hanno un file comune (ad es file.txt.). Voglio cercare una stringa solo in questo file.txtescludendo altri file. Come posso fare questo?

L'ho usato grep -arin "pattern" *, ma è molto lento se abbiamo molte sottodirectory e file.


Risposte:


21

Nella directory principale, è possibile utilizzare finde quindi eseguire grepsolo quei file:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +

2
Suggerisco anche di passare -Ha in grepmodo che, nei casi in cui viene passato solo un percorso, quel percorso sia ancora stampato (piuttosto che solo le righe corrispondenti dal file).
Eliah Kagan,

24

Puoi anche usare globstar.

Costruire grepcomandi con find, come nella risposta di Zanna , è un modo altamente robusto, versatile e portatile per farlo (vedi anche la risposta di sudodus ). E Muru ha pubblicato un ottimo approccio di utilizzo di grep's --includel'opzione . Ma se vuoi usare solo il grepcomando e la tua shell, c'è un altro modo per farlo: puoi fare in modo che la shell esegua la ricorsione necessaria :

shopt -s globstar   # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt

Il -Hflag fa grepmostrare il nome del file anche se viene trovato un solo file corrispondente. È possibile passare il -a, -ie -nle bandiere (dal esempio) per greppure, se questo è quello che vi serve. Ma non passare -ro -Rquando si utilizza questo metodo. È la shell che ricorre alle directory per espandere il modello glob contenente **e nongrep .

Queste istruzioni sono specifiche per la shell Bash. Bash è la shell utente predefinita in Ubuntu (e nella maggior parte degli altri sistemi operativi GNU / Linux), quindi se sei su Ubuntu e non sai quale sia la tua shell, è quasi sicuramente Bash. Sebbene le shell popolari di solito supportino **globi che attraversano directory , non sempre funzionano allo stesso modo. Per ulteriori informazioni, vedere Stéphane Chazelas s' eccellente risposta a Il risultato di ls *, ** ls e ls *** su Unix.SE .

Come funziona

L' attivazione dell'opzione globstar bash shell crea **percorsi di corrispondenza contenenti il ​​separatore di directory ( /). È quindi un glob ricorsivo di directory. In particolare, come man bashspiegato:

Quando l' opzione della shell globstar è abilitata e * viene utilizzato in un contesto di espansione del percorso, due * adiacenti utilizzati come un singolo modello corrisponderanno a tutti i file e zero o più directory e sottodirectory. Se seguito da un /, due * adiacenti corrisponderanno solo alle directory e alle sottodirectory.

Dovresti stare attento con questo, dal momento che puoi eseguire comandi che modificano o eliminano molti più file di quelli che intendi, specialmente se scrivi **quando volevi scrivere *. (È sicuro in questo comando, che non modifica alcun file.) shopt -u globstarDisattiva l'opzione di shell globstar.

Ci sono alcune differenze pratiche tra globstar e find.

findè molto più versatile di globstar. Qualunque cosa tu possa fare con globstar, puoi farlo anche con il findcomando. Mi piace globstar, e talvolta è più conveniente, ma globstar non è un'alternativa generale a find.

Il metodo sopra non appare nelle directory i cui nomi iniziano con a .. A volte non si desidera ricorrere a tali cartelle, ma a volte lo si fa.

Come con un normale globo, la shell crea un elenco di tutti i percorsi corrispondenti e li passa come argomenti al comando ( grep) al posto del globo stesso. Se hai chiamato così tanti file file.txtche il comando risultante sarebbe troppo lungo per l'esecuzione del sistema, allora il metodo sopra fallirà. In pratica avresti bisogno di (almeno) migliaia di tali file, ma potrebbe succedere.

I metodi che utilizzano findnon sono soggetti a questa limitazione, perché:

  • Il modo in cui Zanna costruisce ed esegue un grepcomando con potenzialmente molti argomenti di percorso. Ma se vengono trovati più file di +quanti possano essere elencati in un singolo percorso, l' -execazione -terminated esegue il comando con alcuni dei percorsi, quindi lo esegue di nuovo con alcuni altri percorsi e così via. Nel caso di greping per una stringa in più file, questo produce il comportamento corretto.

    Come il metodo globstar trattato qui, questo stampa tutte le linee corrispondenti, con percorsi anteposti a ciascuna.

  • la via di sudodus corre grepseparatamente per ogni file.txttrovato. Se ci sono molti file, potrebbe essere più lento di altri metodi, ma funziona.

    Tale metodo trova i file e stampa i loro percorsi, seguiti da eventuali linee corrispondenti. Questo è un formato di output diverso dal formato prodotto dal mio metodo, Zanna e Muru .

Ottenere il colore con find

Uno dei vantaggi immediati dell'utilizzo di globstar è, per impostazione predefinita su Ubuntu, grepprodurre output colorato. Ma si può facilmente ottenere questo con find, anche .

Gli account utente in Ubuntu sono creati con un alias che fa grepdavvero funzionare grep --color=auto(corri alias grepper vedere). È una buona cosa che gli alias siano praticamente espansi solo quando li emetti in modo interattivo , ma significa che se vuoi findinvocare grepcon la --colorbandiera, dovrai scriverlo esplicitamente. Per esempio:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +

Potresti voler dichiarare più chiaramente che devi usare la bashshell perché questo funzioni. Si fa a dire che è implicitamente in "l'opzione di shell bash globstar", ma può essere facilmente perso da persone che leggono troppo in fretta.
Stig Hemmer,

Ho rimosso la mia risposta perché ha causato molti commenti critici. Quindi dovresti rimuovere il riferimento ad esso nella tua risposta.
sudodus,

@StigHemmer Grazie - ho chiarito che non tutte le shell hanno questa funzione. Sebbene molte shell (non solo bash) supportino **globi che attraversano directory , la tua critica di base è corretta: la presentazione di **questa risposta è specifica per bash, con shopt che è solo bash e il termine "globstar" è (penso) bash e solo tcsh. All'inizio l'avevo sorpreso a causa di quelle complessità, ma hai ragione che è un po 'confuso. Piuttosto che discuterne a lungo in questa risposta, mi sono collegato a un altro post (abbastanza approfondito) che fa il lavoro pesante.
Eliah Kagan,

@sudodus L'ho fatto, ma spero che sia temporaneo. Io e altri abbiamo trovato preziosa la tua risposta. È vero -e, non dovrebbe essere applicato ai percorsi, ma questo è facilmente risolvibile. Per il primo comando, basta omettere -e. Per il secondo, utilizzare find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;o find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Gli utenti a volte preferiranno la tua strada (con l' -eutilizzo fisso) agli altri, che stampano un percorso per riga corrispondente ; il tuo stampa un percorso per file trovato seguito da greprisultati.
Eliah Kagan,

@sudodus Quindi di per grepnon farà quello che stai facendo. Anche alcune altre critiche erano sbagliate. grep -Hgestito da -execnon si colorerà senza --color(o GREP_COLOR). IEEE 1003.1-2008 non garantisce l' {}espansione in ##### {}:, ma Ubuntu ha GNU find, il che lo fa . Se per te va bene, modificherò il tuo post per correggere il -ebug (e chiarirò il suo caso d'uso) e vedrai se vuoi annullare l'eliminazione. (Ho il rappresentante per visualizzare / modificare i post eliminati.)
Eliah Kagan

18

Non è necessario findper questo; greppuò gestirlo perfettamente bene da solo:

grep "pattern" . -airn --include="file.txt"

Da man grep:

--exclude=GLOB
      Skip  files  whose  base  name  matches  GLOB  (using   wildcard
      matching).   A  file-name  glob  can  use  *,  ?,  and [...]  as
      wildcards, and \ to quote  a  wildcard  or  backslash  character
      literally.

--exclude-from=FILE
      Skip  files  whose  base name matches any of the file-name globs
      read from FILE  (using  wildcard  matching  as  described  under
      --exclude).

--exclude-dir=DIR
      Exclude  directories  matching  the  pattern  DIR from recursive
      searches.

--include=GLOB
      Search  only  files whose base name matches GLOB (using wildcard
      matching as described under --exclude).

Bello - questo sembra il modo migliore. Semplice ed efficiente Vorrei aver saputo (o pensato di controllare la manpage per) questo metodo. Grazie!
Eliah Kagan,

@EliahKagan Sono più sorpreso che Zanna non l'abbia postato: qualche tempo fa avevo mostrato un esempio di questa opzione per un'altra risposta. :)
muru,

2
studente lento, ahimè, ma alla fine ci arrivo, i tuoi insegnamenti non sono completamente sprecati su di me;)
Zanna

Questo è molto semplice e facile da ricordare. Grazie.
Rajesh Keladimath,

Sono d'accordo, questa è la risposta migliore. Devo rimuovere la mia risposta per ridurre la confusione, o lasciare che rimanga per dimostrare che ci sono alternative e cosa si può farefind?
sudodus

8

Il metodo indicato nella risposta di Muru , di correre grepcon il --includeflag per specificare un nome di file, è spesso la scelta migliore. Tuttavia, questo può essere fatto anche con find.

L'approccio in questa risposta utilizza findper eseguire grepseparatamente per ogni file trovato e stampa il percorso di ciascun file esattamente una volta , sopra le righe corrispondenti trovate in ciascun file. (I metodi che stampano il percorso davanti a ogni riga corrispondente sono trattati in altre risposte.)


È possibile modificare la directory nella parte superiore dell'albero delle directory in cui si trovano quei file. Quindi eseguire:

find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \;

Ciò stampa il percorso (relativo alla directory corrente .e includendo il nome file stesso) di ciascun file denominato file.txt, seguito da tutte le righe corrispondenti nel file. Questo funziona perché{} è un segnaposto per il file trovato. Il percorso di ciascun file è separato dal suo contenuto con il prefisso #####e viene stampato una sola volta, prima delle righe corrispondenti di quel file. (I file chiamati file.txtche non contengono corrispondenze hanno ancora i loro percorsi stampati.) Potresti trovare questo output meno disordinato rispetto a quello che ottieni dai metodi che stampano un percorso all'inizio di ogni riga corrispondente.

L'uso in findquesto modo sarà quasi sempre più veloce dell'esecuzione grepsu ogni file ( grep -arin "pattern" *), perché findcerca i file con il nome corretto e salta tutti gli altri file.

Ubuntu usa GNU find , che si espande sempre {}anche quando appare in una stringa più grande , come ##### {}:. Se hai bisogno del tuo comando per lavorare con findsistemi che potrebbero non supportarlo , o preferisci usare l' -execazione solo quando assolutamente necessario, puoi usare:

find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \;

Per facilitare la lettura dell'output , è possibile utilizzare le sequenze di escape ANSI per ottenere nomi di file colorati. In questo modo, l'intestazione del percorso di ciascun file si distingue meglio dalle linee corrispondenti che vengono stampate al suo interno:

find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;

Ciò fa sì che la shell trasformi il codice di escape per il verde nella sequenza di escape effettiva che produce verde in un terminale e fa la stessa cosa con il codice di escape per il colore normale. Queste escape vengono passate a find, che le utilizza quando stampa un nome file. (la $' 'citazione è necessaria qui perchéfind l' -printfazione non riconosce \eper l'interpretazione dei codici di escape ANSI.)

Se si preferisce, si può invece usare -execcon il sistema printfdi comando (che non fa supporto \e). Quindi un altro modo di fare la stessa cosa è:

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;

stavo per fare un "for loop" con un array e non ho pensato all'opzione native exec di find. Buona! Ma penso che l'uso di dot ti localizzerà nella directory in cui ti trovi già. Correggimi se sbaglio. Non sarebbe meglio specificare direttamente l'analisi da trovare nell'ordine di ricerca? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv,

Certo, questo eliminerà il cd abc/def/efgcomando 'cambia directory' :-)
sudodus

(1) Perché stai specificando l' -eopzione echo? Ciò causerà la distruzione di tutti i nomi di file che contengono barre rovesciate. (2) L'uso {}come parte di un argomento non è garantito per funzionare. Sarebbe meglio dire -exec echo "#####" {} \;o -exec printf "##### %s:\n" {} \;. (3) Perché non usare semplicemente -printo -printf? (4) Considera anche grep -H.
G-Man dice "Ripristina Monica" il

@ G-man, 1) Perché originariamente ho usato il colore ANSI: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Potresti avere ragione, ma finora questo funziona per me. 3) -print e -printf sono anche alternative. 4) Questo è già lì nella risposta principale. - Comunque, sei il benvenuto con la tua risposta :-)
sudodus

Non hai bisogno delle due -execchiamate. Basta usare grep -He questo stamperà il nome del file (a colori) e il testo corrispondente.
terdon,

0

Solo per indicare che se le condizioni della domanda possono essere prese letteralmente, puoi usare grep diretto:

grep 'pattern' abc/def/efg/*/file.txt

o

grep 'pattern' abc/def/efg/{1..300}/file.txt
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.