web-dev-qa-db-fra.com

Grep récursif vs find / -type f -exec grep {} \; Quel est le plus efficace / le plus rapide?

Quel est le plus efficace pour trouver les fichiers d'un système de fichiers complet qui contiennent une chaîne: grep récursif ou find avec grep dans une instruction exec? Je suppose que find serait plus efficace car vous pouvez au moins effectuer un filtrage si vous connaissez l'extension du fichier ou une expression régulière qui correspond au nom du fichier, mais lorsque vous ne connaissez que -type f ce qui est mieux? GNU grep 2.6.3; find (GNU findutils) 4.4.2

Exemple:

grep -r -i 'the brown dog' /

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

73
Gregg Leventhal

Je ne suis pas sûr:

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

c'est vraiment ce que vous vouliez dire. Cela signifierait grep récursivement dans tous les fichiers et répertoires non cachés dans / (Mais toujours regarder à l'intérieur des fichiers cachés et des répertoires à l'intérieur de ceux-ci).

En supposant que vous vouliez dire:

grep -r -i 'the brown dog' /

Quelques points à noter:

  • Toutes les implémentations grep ne prennent pas en charge -r. Et parmi ceux qui le font, les comportements diffèrent: certains suivent les liens symboliques vers les répertoires lors de la traversée de l'arborescence des répertoires (ce qui signifie que vous pouvez finir par regarder plusieurs fois dans le même fichier ou même exécuter en boucles infinies), d'autres non. Certains regarderont à l'intérieur des fichiers de périphérique (et cela prendra un certain temps dans /dev/zero Par exemple) ou des tuyaux ou des fichiers binaires ..., d'autres non.
  • C'est efficace car grep commence à regarder à l'intérieur des fichiers dès qu'il les découvre. Mais alors qu'il cherche dans un fichier, il ne cherche plus de fichiers à rechercher (ce qui est probablement aussi bien dans la plupart des cas)

Votre:

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

(supprimé le -r qui n'avait aucun sens ici) est terriblement inefficace car vous exécutez un grep par fichier. ; Ne doit être utilisé que pour les commandes qui n'acceptent qu'un seul argument. De plus ici, parce que grep ne regarde que dans un seul fichier, il n'imprimera pas le nom du fichier, donc vous ne saurez pas où sont les correspondances.

Vous ne regardez pas à l'intérieur des fichiers de périphérique, des tuyaux, des liens symboliques ..., vous ne suivez pas les liens symboliques, mais vous regardez toujours potentiellement à l'intérieur de choses comme /proc/mem.

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

serait beaucoup mieux car le moins de commandes grep que possible seraient exécutées. Vous obtiendrez le nom du fichier à moins que la dernière exécution n'ait qu'un seul fichier. Pour cela il vaut mieux utiliser:

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

ou avec GNU grep:

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

Notez que grep ne sera pas démarré tant que find n'aura pas trouvé suffisamment de fichiers à mâcher, il y aura donc un certain retard initial. Et find ne poursuivra pas la recherche de fichiers supplémentaires tant que le précédent grep ne sera pas revenu. L'allocation et la transmission de la liste des gros fichiers ont un impact (probablement négligeable), donc dans l'ensemble, cela sera probablement moins efficace qu'un grep -r Qui ne suit pas le lien symbolique ou ne regarde pas à l'intérieur des appareils.

Avec les outils GNU:

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

Comme ci-dessus, le moins d'exemples grep possibles seront exécutés, mais find continuera à rechercher plus de fichiers tandis que la première invocation grep cherchera à l'intérieur du premier lot. Cela peut ou non être un avantage. Par exemple, avec des données stockées sur des disques durs rotatifs, find et grep l'accès aux données stockées à différents emplacements sur le disque ralentira le débit du disque en provoquant un déplacement constant de la tête de disque. Dans une configuration RAID (où find et grep peuvent accéder à différents disques) ou sur des SSD, cela peut faire une différence positive.

Dans une configuration RAID, l'exécution de plusieurs invocations simultanéesgrep peut également améliorer les choses. Toujours avec GNU sur le stockage RAID1 avec 3 disques,

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

pourrait augmenter considérablement les performances. Notez cependant que le deuxième grep ne sera lancé qu'une fois que suffisamment de fichiers auront été trouvés pour remplir la première commande grep. Vous pouvez ajouter une option -n À xargs pour que cela se produise plus tôt (et passer moins de fichiers par grep invocation).

Notez également que si vous redirigez la sortie xargs vers autre chose qu'un périphérique terminal, les grepss commenceront à mettre en mémoire tampon leur sortie, ce qui signifie que la sortie de ces greps sera probablement être mal entrelacé. Vous devez utiliser stdbuf -oL (Si disponible comme sur GNU ou FreeBSD) sur eux pour contourner ce problème (vous pouvez toujours avoir des problèmes avec les très longues lignes (généralement> 4KiB )) ou demandez à chacun d'écrire leur sortie dans un fichier séparé et de les concaténer tous à la fin.

Ici, la chaîne que vous recherchez est fixe (pas une expression rationnelle), donc l'utilisation de l'option -F Pourrait faire une différence (peu probable car les implémentations grep savent déjà déjà l'optimiser).

Une autre chose qui pourrait faire une grande différence est de fixer la locale à C si vous êtes dans une locale à plusieurs octets:

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

Pour éviter de regarder à l'intérieur de /proc, /sys ..., utilisez -xdev Et spécifiez les systèmes de fichiers dans lesquels vous souhaitez effectuer la recherche:

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

Ou élaguez les chemins que vous souhaitez exclure explicitement:

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

Si la * dans l'appel grep n'est pas important pour vous, alors la première devrait être plus efficace car une seule instance de grep est démarrée et les fourches ne sont pas gratuites. Dans la plupart des cas, ce sera plus rapide même avec le * mais dans les cas Edge, le tri pourrait inverser cela.

Il peut y avoir d'autres structures find-grep qui fonctionnent mieux, en particulier avec de nombreux petits fichiers. La lecture de grandes quantités d'entrées de fichier et d'inodes à la fois peut améliorer les performances des supports en rotation.

Mais regardons les statistiques de syscall:

trouver

> 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

grep uniquement

> 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
14
Hauke Laging

Si vous êtes sur un SSD et que le temps de recherche est négligeable, vous pouvez utiliser GNU parallel:

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

Cela exécutera jusqu'à 8 processus grep en même temps en fonction de ce que find a trouvé.

Cela va détruire un disque dur, mais un SSD devrait assez bien y faire face.

5
Naftuli Kay