Grep ricorsivo vs find / -type f -exec grep {} \; Qual è più efficiente / più veloce?


70

Quale è più efficace per trovare quali file in un intero filesystem contengono una stringa: grep ricorsivo o find con grep in un'istruzione exec? Presumo che trovare sarebbe più efficiente perché puoi almeno fare un po 'di filtraggio se conosci l'estensione del file o una regex che corrisponde al nome del file, ma quando sai solo -type fquale è meglio? GNU grep 2.6.3; find (GNU findutils) 4.4.2

Esempio:

grep -r -i 'the brown dog' /

find / -type f -exec grep -i 'the brown dog' {} \;


1
L'efficienza della matematica / informatica / algoritmo non è basata sull'opinione.
Gregg Leventhal,

Controlla questo. Sebbene non ricorsivo, darebbe una comprensione su quale sia il migliore. unix.stackexchange.com/questions/47983/…
Ramesh,

8
@AvinashRaj non sta chiedendo un parere. Sta chiedendo quale sia più efficiente e / o più veloce , non quale sia "migliore". Questa è una domanda perfettamente rispondente che ha una singola risposta specifica che dipende da come questi due programmi svolgono il loro lavoro e da cosa esattamente dai loro per cercare.
terdon

2
Si noti che il -exec {} +modulo eseguirà meno fork, quindi dovrebbe essere più veloce di -exec {} \;. Potrebbe essere necessario aggiungere -H(o -h) alle grepopzioni per ottenere un output esattamente equivalente.
Mikel,

Probabilmente non volevi l' -ropzione grepper il secondo
qwertzguy l'

Risposte:


85

Non ne sono sicuro:

grep -r -i 'the brown dog' /*

è davvero quello che volevi dire. Ciò significherebbe grep in modo ricorsivo in tutti i file e le directory non nascosti /(ma cerca comunque all'interno di file e directory nascosti all'interno di quelli).

Supponendo che volevi dire:

grep -r -i 'the brown dog' /

Alcune cose da notare:

  • Non tutte le grepimplementazioni supportano -r. E tra quelli che lo fanno, i comportamenti differiscono: alcuni seguono collegamenti simbolici alle directory quando si attraversa l'albero delle directory (il che significa che si può finire per cercare più volte nello stesso file o addirittura eseguire cicli infiniti), altri no. Alcuni guarderanno all'interno dei file del dispositivo (e ci vorrà del tempo /dev/zeroper esempio) o pipe o file binari ..., altri no.
  • È efficiente poiché grepinizia a cercare i file non appena li rileva. Ma mentre cerca in un file, non è più alla ricerca di più file in cui cercare (che probabilmente è altrettanto valido nella maggior parte dei casi)

Il tuo:

find / -type f -exec grep -i 'the brown dog' {} \;

(rimosso ciò -rche non aveva senso qui) è terribilmente inefficiente perché ne stai eseguendo uno grepper file. ;dovrebbe essere usato solo per comandi che accettano solo un argomento. Inoltre qui, poiché grepguarda solo in un file, non stamperà il nome del file, quindi non saprai dove si trovano le partite.

Non stai cercando all'interno dei file del dispositivo, pipe, symlink ..., non stai seguendo i symlink, ma stai ancora potenzialmente cercando cose del genere /proc/mem.

find / -type f -exec grep -i 'the brown dog' {} +

sarebbe molto meglio perché grepverrebbero eseguiti meno comandi possibili. Otterresti il ​​nome del file a meno che l'ultima esecuzione non abbia un solo file. Per questo è meglio usare:

find / -type f -exec grep -i 'the brown dog' /dev/null {} +

o con GNU grep:

find / -type f -exec grep -Hi 'the brown dog' {} +

Nota che grepnon verrà avviato fino a quando non findavrà trovato abbastanza file per poterlo masticare, quindi ci sarà qualche ritardo iniziale. E findnon continuerà a cercare altri file fino alla greprestituzione del precedente . L'allocazione e il passaggio dell'elenco di file di grandi dimensioni ha un impatto (probabilmente trascurabile), quindi nel complesso sarà probabilmente meno efficiente di un grep -rche non segue il collegamento simbolico o guarda all'interno dei dispositivi.

Con gli strumenti GNU:

find / -type f -print0 | xargs -r0 grep -Hi 'the brown dog'

Come sopra, grepverranno eseguiti il minor numero di casi possibili, ma findcontinuerà a cercare altri file mentre la prima grepchiamata viene eseguita all'interno del primo batch. Questo può essere o non essere un vantaggio però. Ad esempio, con i dati memorizzati su dischi rigidi di rotazione finde l' grepaccesso ai dati memorizzati in diverse posizioni sul disco rallenterà il throughput del disco causando lo spostamento costante della testina del disco. In una configurazione RAID (dove finde greppossono accedere a diversi dischi) o su SSD, ciò potrebbe fare la differenza.

In una configurazione RAID, anche l'esecuzione di più invocazioni simultanee grep potrebbe migliorare le cose. Sempre con strumenti GNU su storage RAID1 con 3 dischi,

find / -type f -print0 | xargs -r0 -P2 grep -Hi 'the brown dog'

potrebbe aumentare significativamente le prestazioni. Si noti tuttavia che il secondo grepverrà avviato solo dopo aver trovato abbastanza file per riempire il primo grepcomando. È possibile aggiungere -nun'opzione affinché xargsciò accada prima (e passare meno file per grepinvocazione).

Si noti inoltre che se si reindirizza l' xargsoutput a qualcosa che non sia un dispositivo terminale, allora gli grepss inizieranno a bufferizzare il loro output, il che significa che grepprobabilmente l'output di questi s verrà intercalato erroneamente. Dovresti usare stdbuf -oL(dove disponibile come su GNU o FreeBSD) su di essi per aggirare ciò (potresti ancora avere problemi con linee molto lunghe (in genere> 4KiB)) o avere ciascuno scrivere il proprio output in un file separato e concatenarli tutto alla fine.

Qui, la stringa che stai cercando è fissa (non una regexp) quindi l'utilizzo -Fdell'opzione potrebbe fare la differenza (è improbabile poiché le grepimplementazioni sanno già come ottimizzarla).

Un'altra cosa che potrebbe fare una grande differenza è correggere le impostazioni locali su C se ci si trova in una locale multi-byte:

find / -type f -print0 | LC_ALL=C xargs -r0 -P2 grep -Hi 'the brown dog'

Per evitare di guardarti dentro /proc, /sys..., usa -xdeve specifica i file system in cui vuoi cercare:

LC_ALL=C find / /home -xdev -type f -exec grep -i 'the brown dog' /dev/null {} +

O pota i percorsi che vuoi escludere esplicitamente:

LC_ALL=C find / \( -path /dev -o -path /proc -o -path /sys \) -prune -o \
  -type f -exec grep -i 'the brown dog' /dev/null {} +

Suppongo che qualcuno non possa indicarmi una risorsa - o spiegare - cosa significano {} e +. Non c'è niente che io possa vedere nelle pagine man di exec, grep o trovo sulla scatola di Solaris che sto usando. La shell sta semplicemente concatenando i nomi dei file e li passa a grep?

3
@Poldie, questo è chiaramente spiegato alla descrizione del -execpredicato nella pagina man di Solaris
Stéphane Chazelas,

Ah sì. Non stavo sfuggendo al mio {carattere mentre cercavo nella pagina man. Il tuo link è migliore; Trovo pagine man terribili da leggere.

1
RAID1 con 3 dischi? Che strano ...
tink

1
@ink, sì RAID1 è su 2 o più dischi. Con 3 dischi rispetto a 2 dischi, aumenti la ridondanza e le prestazioni di lettura mentre le prestazioni di scrittura sono all'incirca le stesse. Con 3 dischi invece di 2, ciò significa che puoi anche correggere gli errori, come quando un po 'si gira su una delle copie, sei in grado di dire quale è giusto controllando tutte e 3 le copie mentre con 2 dischi, non puoi dirlo davvero.
Stéphane Chazelas,

13

Se la *nella grepchiamata non è importante per voi, allora il primo dovrebbe essere più efficiente come una sola istanza di grepè iniziato, e le forcelle non sono liberi. Nella maggior parte dei casi sarà più veloce anche con il caso *ma nei casi limite l'ordinamento potrebbe invertire ciò.

Ci possono essere altri find- grepstrutture che funzionano meglio soprattutto con molti file di piccole dimensioni. La lettura simultanea di grandi quantità di voci e inode dei file può migliorare le prestazioni dei supporti rotanti.

Ma diamo un'occhiata alle statistiche di syscall:

trova

> strace -cf find . -type f -exec grep -i -r 'the brown dog' {} \;
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 97.86    0.883000        3619       244           wait4
  0.53    0.004809           1      9318      4658 open
  0.46    0.004165           1      6875           mmap
  0.28    0.002555           3       977       732 execve
  0.19    0.001677           2       980       735 stat
  0.15    0.001366           1      1966           mprotect
  0.09    0.000837           0      1820           read
  0.09    0.000784           0      5647           close
  0.07    0.000604           0      5215           fstat
  0.06    0.000537           1       493           munmap
  0.05    0.000465           2       244           clone
  0.04    0.000356           1       245       245 access
  0.03    0.000287           2       134           newfstatat
  0.03    0.000235           1       312           openat
  0.02    0.000193           0       743           brk
  0.01    0.000082           0       245           arch_prctl
  0.01    0.000050           0       134           getdents
  0.00    0.000045           0       245           futex
  0.00    0.000041           0       491           rt_sigaction
  0.00    0.000041           0       246           getrlimit
  0.00    0.000040           0       489       244 ioctl
  0.00    0.000038           0       591           fcntl
  0.00    0.000028           0       204       188 lseek
  0.00    0.000024           0       489           set_robust_list
  0.00    0.000013           0       245           rt_sigprocmask
  0.00    0.000012           0       245           set_tid_address
  0.00    0.000000           0         1           uname
  0.00    0.000000           0       245           fchdir
  0.00    0.000000           0         2         1 statfs
------ ----------- ----------- --------- --------- ----------------
100.00    0.902284                 39085      6803 total

solo grep

> strace -cf grep -r -i 'the brown dog' .
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 40.00    0.000304           2       134           getdents
 31.71    0.000241           0       533           read
 18.82    0.000143           0       319         6 openat
  4.08    0.000031           4         8           mprotect
  3.29    0.000025           0       199       193 lseek
  2.11    0.000016           0       401           close
  0.00    0.000000           0        38        19 open
  0.00    0.000000           0         6         3 stat
  0.00    0.000000           0       333           fstat
  0.00    0.000000           0        32           mmap
  0.00    0.000000           0         4           munmap
  0.00    0.000000           0         6           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0       245       244 ioctl
  0.00    0.000000           0         1         1 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0       471           fcntl
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           futex
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0       132           newfstatat
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.000760                  2871       466 total

1
Sulla scala della ricerca di un intero file system, le forcelle sono trascurabili. L'I / O è ciò che si desidera ridurre.
Gilles 'SO- smetti di essere malvagio'

Sebbene si tratti di un errore dall'OP, il confronto non è corretto, è necessario rimuovere la -rbandiera di grepquando si utilizza find. Puoi vedere che ha cercato più e più volte gli stessi file confrontando il numero di openquello che è successo.
qwertzguy,

1
@qwertzguy, no, -rdovrebbe essere innocuo poiché le -type fgaranzie nessuno degli argomenti sono directory. I multipli open()sono più probabili fino agli altri file aperti da grepogni invocazione (librerie, dati di localizzazione ...) (grazie per la modifica sulla mia risposta tra l'altro)
Stéphane Chazelas,

5

Se sei su un SSD e il tempo di ricerca è trascurabile, puoi usare GNU parallel:

find /path -type f | parallel --gnu --workdir "$PWD" -j 8 '
    grep -i -r 'the brown dog' {} 
'

Questo eseguirà fino a 8 processi grep contemporaneamente in base a quanto findtrovato.

Questo si romperà un disco rigido, ma un SSD dovrebbe farcela abbastanza bene.


-1

Un'altra cosa da considerare al riguardo è la seguente.

Qualcuno delle directory che grep dovrà ricorrere in modo ricorsivo conterrà più file dell'impostazione nofile del tuo sistema ? (es. numero di handle di file aperti, il valore predefinito è 1024 sulla maggior parte delle distribuzioni linux)

Se è così, allora trovare è sicuramente la strada da percorrere poiché alcune versioni di grep esploderanno con un errore troppo lungo nella lista degli Argomenti quando colpisce una directory con più file rispetto all'impostazione massima di handle di file aperti.

Solo il mio 2 ¢.


1
Perché grepesplodere? Almeno con GNU grep se si fornisce un percorso con il trailing /e lo si usa, -Rsi passerà semplicemente attraverso le directory. La shell non espanderà nulla a meno che non si forniscano globi-shell. Quindi nell'esempio dato ( /*) solo i contenuti della /materia, non delle sottocartelle che verranno semplicemente enumerati grep, non passati come argomento dalla shell.
0xC0000022L

Bene, considerando che l'OP stava chiedendo di cercare ricorsivamente (es. "Grep -r -i 'the brown dog' / *"), ho visto il grep di GNU (almeno la versione 2.9) esplodere con: "- bash: / bin / grep: Elenco argomenti troppo lungo "usando l'esatta ricerca utilizzata dall'OP su una directory che conteneva oltre 140.000 sottodirectory.
B.Kaatz,
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.