Grep recursivo vs find / -type f -exec grep {} \; ¿Cuál es más eficiente / más rápido?

70

¿Qué es más eficiente para encontrar qué archivos en un sistema de archivos completo contienen una cadena: grep recursiva o encontrar con grep en una declaración ejecutiva? Supongo que find sería más eficiente porque al menos puede filtrar si conoce la extensión del archivo o una expresión regular que coincide con el nombre del archivo, pero cuando solo sabe -type fcuál es mejor. GNU grep 2.6.3; find (GNU findutils) 4.4.2

Ejemplo:

grep -r -i 'the brown dog' /

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

Gregg Leventhal
fuente
1
La eficiencia matemática / informática / algoritmo no se basa en la opinión.
Gregg Leventhal
Revisa este. Aunque no es recursivo, daría una idea de cuál es mejor. unix.stackexchange.com/questions/47983/…
Ramesh
8
@AvinashRaj no está pidiendo opinión. Él pregunta cuál es más eficiente y / o más rápido , no cuál es "mejor". Esta es una pregunta perfectamente respondible que tiene una respuesta única y específica que depende de cómo estos dos programas hacen su trabajo y de qué les das exactamente para buscar.
terdon
2
Tenga en cuenta que el -exec {} +formulario hará menos bifurcaciones, por lo que debería ser más rápido que -exec {} \;. Es posible que deba agregar -H(o -h) a las grepopciones para obtener un resultado exactamente equivalente.
Mikel
Probablemente no quería la -ropción greppara la segunda
qwertzguy

Respuestas:

85

No estoy seguro:

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

es realmente lo que quisiste decir. Eso significaría grep recursivamente en todos los archivos y directorios no ocultos /(pero aún mira dentro de los archivos y directorios ocultos dentro de ellos).

Suponiendo que quisieras decir:

grep -r -i 'the brown dog' /

Algunas cosas a tener en cuenta:

  • No todas las grepimplementaciones son compatibles -r. Y entre los que lo hacen, los comportamientos difieren: algunos siguen enlaces simbólicos a directorios cuando atraviesan el árbol de directorios (lo que significa que puede terminar buscando varias veces en el mismo archivo o incluso ejecutarse en bucles infinitos), otros no. Algunos buscarán dentro de los archivos del dispositivo (y llevará bastante tiempo, /dev/zeropor ejemplo) o tuberías o archivos binarios ..., otros no.
  • Es eficiente ya que grepcomienza a buscar dentro de los archivos tan pronto como los descubre. Pero mientras se ve en un archivo, ya no busca más archivos para buscar (lo que probablemente sea igual de bueno en la mayoría de los casos)

Tu:

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

(eliminado el -rque no tenía sentido aquí) es terriblemente ineficiente porque está ejecutando uno greppor archivo. ;solo debe usarse para comandos que aceptan solo un argumento. Además, aquí, debido a que grepsolo se ve en un archivo, no imprimirá el nombre del archivo, por lo que no sabrá dónde están las coincidencias.

No estás buscando dentro de los archivos del dispositivo, tuberías, enlaces simbólicos ..., no estás siguiendo los enlaces simbólicos, pero aún estás potencialmente buscando cosas como /proc/mem.

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

sería mucho mejor porque grepse ejecutarían la menor cantidad de comandos posible. Obtendría el nombre del archivo a menos que la última ejecución tenga solo un archivo. Para eso es mejor usar:

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

o con GNU grep:

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

Tenga en cuenta que grepno se iniciará hasta que findhaya encontrado suficientes archivos para masticar, por lo que habrá un retraso inicial. Y findno continuará buscando más archivos hasta que el anterior grephaya regresado. La asignación y aprobación de la lista de archivos grandes tiene un impacto (probablemente insignificante), por lo que, en general, será menos eficiente que un grep -renlace simbólico o que no mire dentro de los dispositivos.

Con herramientas GNU:

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

Como se indicó anteriormente, grepse ejecutarán la menor cantidad posible de instancias, pero findcontinuarán buscando más archivos mientras la primera grepinvocación se encuentra dentro del primer lote. Sin embargo, eso puede o no ser una ventaja. Por ejemplo, con los datos almacenados en discos duros rotativos, findy el grepacceso a los datos almacenados en diferentes ubicaciones del disco disminuirá el rendimiento del disco al hacer que el cabezal del disco se mueva constantemente. En una configuración RAID (donde findy greppuede acceder a diferentes discos) o en SSD, eso podría marcar una diferencia positiva.

En una configuración RAID, ejecutar varias invocaciones concurrentes grep también podría mejorar las cosas. Aún con herramientas GNU en almacenamiento RAID1 con 3 discos,

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

podría aumentar el rendimiento significativamente. Sin embargo, greptenga en cuenta que el segundo solo se iniciará una vez que se hayan encontrado suficientes archivos para completar el primer grepcomando. Puede agregar una -nopción xargspara que eso suceda antes (y pasar menos archivos por grepinvocación).

También tenga en cuenta que si está redirigiendo la xargssalida a cualquier cosa que no sea un dispositivo terminal, entonces los grepss comenzarán a almacenar en búfer su salida, lo que significa que la salida de esos greps probablemente se entrelazará incorrectamente. Tendría que usar stdbuf -oL(cuando esté disponible, como en GNU o FreeBSD) en ellos para solucionarlo (aún puede tener problemas con líneas muy largas (generalmente> 4KiB)) o hacer que cada uno escriba su salida en un archivo separado y los concatene todo al final.

Aquí, la cadena que está buscando es fija (no es una expresión regular), por lo que usar la -Fopción puede hacer la diferencia (es poco probable ya que las grepimplementaciones ya saben cómo optimizar eso).

Otra cosa que podría marcar una gran diferencia es arreglar la configuración regional en C si está en una configuración regional de varios bytes:

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

Para evitar mirar dentro /proc, /sys..., use -xdevy especifique los sistemas de archivos en los que desea buscar:

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

O pode los caminos que desea excluir explícitamente:

LC_ALL=C find / \( -path /dev -o -path /proc -o -path /sys \) -prune -o \
  -type f -exec grep -i 'the brown dog' /dev/null {} +
Stéphane Chazelas
fuente
No creo que alguien pueda señalarme un recurso, o explicar, qué significan {} y +. No hay nada que pueda ver en las páginas man para exec, grep o encontrar en el cuadro de Solaris que estoy usando. ¿Es solo el shell que concatena los nombres de archivo y los pasa a grep?
3
@Poldie, eso se explica claramente en la descripción del -execpredicado en la página de manual de Solaris
Stéphane Chazelas
Ah, sí. No estaba escapando de mi {char mientras buscaba en la página de manual. Tu enlace es mejor; Me parecen terribles las páginas del manual.
1
RAID1 con 3 discos? Qué extraño ...
tink
1
@tink, sí RAID1 está en 2 o más discos. Con 3 discos en comparación con 2 discos, aumenta la redundancia y el rendimiento de lectura, mientras que el rendimiento de escritura es aproximadamente el mismo. Con 3 discos en lugar de 2, eso significa que también puede corregir errores, ya que cuando un bit se voltea en una de las copias, puede saber cuál es el correcto al verificar las 3 copias, mientras que con 2 discos, no puede realmente decir
Stéphane Chazelas
13

Si el *de la grepllamada no es importante para ti, entonces el primero debe ser más eficiente ya que sólo una instancia de grepque se inicie, y horquillas no son libres. En la mayoría de los casos, será más rápido incluso con el caso, *pero en casos extremos, la clasificación podría revertir eso.

Puede haber otros find- grepestructuras que funcionan mejor, especialmente con muchos archivos pequeños. La lectura de grandes cantidades de entradas de archivo e inodos a la vez puede mejorar el rendimiento de los medios rotativos.

Pero echemos un vistazo a las estadísticas de syscall:

encontrar

> 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
Hauke ​​Laging
fuente
1
En la escala de búsqueda en un sistema de archivos completo, los tenedores son insignificantes. I / O es lo que desea reducir.
Gilles 'SO- deja de ser malvado'
Aunque es un error del OP, la comparación es incorrecta, debe eliminar el -rindicador de grepcuando se usa find. Puede ver que buscó una y otra vez los mismos archivos al comparar el número de lo opensucedido.
qwertzguy
1
@qwertzguy, no, -rdebería ser inofensivo ya que las -type fgarantías de que ninguno de los argumentos son directorios. Los múltiples open()s son más probables hasta los otros archivos abiertos por grepcada invocación (bibliotecas, datos de localización ...) (gracias por la edición en mi respuesta por cierto)
Stéphane Chazelas
5

Si está en un SSD y el tiempo de búsqueda es insignificante, podría usar GNU paralelo:

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

Esto ejecutará hasta 8 procesos grep al mismo tiempo según lo findencontrado.

Esto golpeará una unidad de disco duro, pero un SSD debería hacer frente bastante bien.

Naftuli Kay
fuente
-1

Una cosa más a considerar en este caso es la siguiente.

¿Alguno de los directorios por los que grep tendrá que pasar recursivamente contendrá más archivos que la configuración de nofile de su sistema ? (por ejemplo, número de identificadores de archivos abiertos, el valor predeterminado es 1024 en la mayoría de las distribuciones de Linux)

Si es así, entonces encontrar es definitivamente el camino a seguir, ya que ciertas versiones de grep bombardearán con un error de lista de argumentos demasiado largo cuando llegue a un directorio con más archivos que la configuración máxima de identificadores de archivos abiertos.

Solo mi 2 ¢.

B.Kaatz
fuente
1
¿Por qué grepbombardear? Al menos con GNU grep si le das una ruta con el rastreo /y la usas -Rsimplemente iterará a través de los directorios. El caparazón no va a expandir nada a menos que le des globs. Entonces, en el ejemplo dado ( /*) solo los contenidos de la /materia, no de las subcarpetas que simplemente se enumerarán grep, no se pasarán como argumento desde el shell.
0xC0000022L
Bueno, considerando que el OP estaba preguntando sobre la búsqueda recursiva (por ejemplo, "grep -r -i 'the brown dog' / *"), he visto la bomba grep de GNU (al menos la Versión 2.9) con: "- bash: / bin / grep: Lista de argumentos demasiado larga "utilizando la búsqueda exacta que el OP utilizó en un directorio que tenía más de 140,000 subdirectorios.
B.Kaatz