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

70

Qu'est-ce qui est plus efficace pour rechercher quels fichiers d'un système de fichiers entier contiennent une chaîne: grep récursif ou rechercher avec grep dans une instruction exec? Je suppose que find serait plus efficace car vous pouvez au moins faire un filtrage si vous connaissez l'extension de fichier ou une expression rationnelle qui correspond au nom du fichier, mais quand vous savez seulement -type fqui est le meilleur? 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' {} \;

Gregg Leventhal
la source
1
Maths / informatique / efficacité des algorithmes non basée sur l'opinion.
Gregg Leventhal
Vérifier celui-ci. Bien que non récursif, cela donnerait une compréhension de ce qui est préférable. unix.stackexchange.com/questions/47983/…
Ramesh
8
@AvinashRaj il ne demande pas d'opinion. Il demande ce qui est le plus efficace et / ou le plus rapide , et non celui qui est "meilleur". C’est une question parfaitement explicite qui a une réponse unique et spécifique qui dépend de la manière dont ces deux programmes font leur travail et de ce que vous leur donnez exactement.
terdon
2
Notez que le -exec {} +formulaire fera moins de forks, devrait donc être plus rapide que -exec {} \;. Vous devrez peut-être ajouter -H(ou -h) aux grepoptions pour obtenir une sortie exactement équivalente.
Mikel
Vous ne vouliez probablement pas que l' -roption soit activée greppour le second
qwertzguy le

Réponses:

85

Je ne suis pas sûr:

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

c'est vraiment ce que vous vouliez dire. Cela signifierait que grep récursivement dans tous les fichiers et répertoires non cachés de /(mais qu'il faut tout de même regarder à l'intérieur des fichiers et des répertoires cachés).

En supposant que vous vouliez dire:

grep -r -i 'the brown dog' /

Quelques points à noter:

  • Toutes les grepimplémentations ne prennent pas en charge -r. Et parmi ceux qui le font, les comportements diffèrent: certains suivent les liens symboliques vers des répertoires lorsqu’ils parcourent l’arborescence (ce qui signifie que vous pouvez chercher plusieurs fois dans le même fichier ou même exécuter des boucles infinies), d’autres ne le feront pas. Certains vont regarder à l'intérieur des fichiers de périphériques (cela prendra un certain temps, /dev/zeropar exemple), des pipes ou des fichiers binaires ..., d'autres pas.
  • C'est efficace, car il grepcommence à 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 dans lesquels chercher (ce qui est probablement aussi bien dans la plupart des cas)

Votre:

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

(supprimé le -rqui n'a pas de sens ici) est terriblement inefficace parce que vous en exécutez un greppar fichier. ;ne devrait être utilisé que pour les commandes qui n'acceptent qu'un seul argument. De plus ici, comme il grepne regarde que dans un seul fichier, le nom du fichier ne sera pas imprimé, vous ne saurez pas où se trouvent les correspondances.

Vous ne regardez pas dans les fichiers de périphérique, les pipes, les liens symboliques ..., vous ne suivez pas les liens symboliques, mais vous recherchez toujours des éléments tels que /proc/mem.

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

serait beaucoup mieux parce que le moins de grepcommandes possible serait exécuté. Vous obtiendrez le nom du fichier à moins que la dernière exécution ne comporte 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 grepcela ne sera pas démarré avant d' findavoir trouvé suffisamment de fichiers pour qu'il puisse être mâché, il y aura donc un délai initial. Et findne poursuivra pas la recherche d'autres fichiers tant que le précédent grepn'est pas revenu. L'attribution et le passage de la liste des gros fichiers ont un impact (probablement négligeable), donc dans l'ensemble, cela sera probablement moins efficace qu'un grep -rlien qui ne suit pas un lien symbolique ou ne regarde pas à l'intérieur des périphériques.

Avec les outils GNU:

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

Comme ci-dessus, le moins d’ grepinstances possibles seront exécutées, mais findcontinueront à rechercher davantage de fichiers pendant que la première grepinvocation est effectuée à l’intérieur du premier lot. Cela peut ou peut ne pas être un avantage si. Par exemple, avec des données stockées sur les disques durs de rotation, findet l' grepaccès aux données stockées à différents endroits sur le disque va ralentir le débit du disque en provoquant la tête du disque de se déplacer en permanence. Dans une configuration RAID (où findet greppeut 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 appels simultanés grep peut également améliorer les choses. Toujours avec des outils GNU sur un stockage RAID1 avec 3 disques,

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

pourrait augmenter la performance de manière significative. Notez cependant que le second grepne sera démarré que lorsque suffisamment de fichiers auront été trouvés pour remplir la première grepcommande. Vous pouvez ajouter une -noption à xargspour que cela se produise plus tôt (et transmettre moins de fichiers par grepappel).

Notez également que si vous redirigez la xargssortie vers un périphérique autre qu'un terminal, le grepss commencera à mettre en tampon leur sortie, ce qui signifie que la sortie de ces greps sera probablement mal entrelacée. Vous devriez les utiliser stdbuf -oL(là où ils sont disponibles, comme sur GNU ou FreeBSD) pour résoudre ce problème (vous pouvez toujours rencontrer des problèmes avec de très longues lignes (généralement> 4 Ko)) ou demandez à chacun d’écrire leur sortie dans un fichier séparé et de les concaténer. tout à la fin.

Ici, la chaîne que vous recherchez est fixe (pas une expression rationnelle), donc l'utilisation de l' -Foption pourrait faire la différence (peu probable que les grepimplémentations sachent déjà l'optimiser).

Une autre chose qui pourrait faire une grande différence est de fixer les paramètres régionaux sur C si vous êtes dans des paramètres régionaux multi-octets:

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

Pour éviter de regarder à l'intérieur /proc, /sys..., utilisez -xdevet 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 {} +
Stéphane Chazelas
la source
Je suppose que personne ne peut m'indiquer une ressource - ni expliquer - ce que {} et + signifient. Il n'y a rien que je puisse voir dans les pages de manuel relatives à exec, grep ou que je trouve sur la boîte Solaris que j'utilise. Est-ce juste le shell concaténant les noms de fichiers et les passant à grep?
3
@Poldie, cela est clairement expliqué dans la description du -execprédicat dans la page de manuel Solaris
Stéphane Chazelas
Ah oui. Je n'échappais pas à mon {caractère pendant la recherche dans la page de manuel. Votre lien est meilleur; Je trouve les pages man terribles à lire.
1
RAID1 avec 3 disques? Comme c'est étrange ...
tink
1
@tink, oui RAID1 est sur 2 disques ou plus. Avec 3 disques comparés à 2 disques, vous augmentez la redondance et les performances de lecture alors que les performances d’écriture sont à peu près identiques. Avec 3 disques au lieu de 2, cela signifie que vous pouvez également corriger les erreurs, comme quand un peu tourne sur l'une des copies, vous êtes en mesure de dire ce qui est juste en vérifiant les 3 copies alors qu'avec 2 disques, vous ne pouvez pas vraiment dire.
Stéphane Chazelas
13

Si l' appel *dans l' grepappel n'est pas important pour vous, alors le premier devrait être plus efficace, car une seule instance de grepest démarrée et les forks ne sont pas gratuits. Dans la plupart des cas, le traitement sera plus rapide, même dans les *cas extrêmes, mais le tri pourrait inverser la tendance.

Il peut y avoir d’autres find- grepstructures qui fonctionnent mieux, surtout avec beaucoup de petits fichiers. La lecture simultanée de grandes quantités d’entrées de fichier et d’inodes peut améliorer les performances des supports en rotation.

Mais regardons les statistiques d'appels système:

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

seulement 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
la source
1
Sur l'échelle de la recherche d'un système de fichiers entier, les fourches sont négligeables. I / O est ce que vous voulez réduire.
Gilles 'SO- arrête d'être méchant'
Bien que ce soit une erreur de l'OP, la comparaison est incorrecte, vous devez supprimer l' -rindicateur de greplors de l'utilisation find. Vous pouvez voir qu'il a recherché à plusieurs reprises les mêmes fichiers en comparant le nombre de opences événements.
Qwertzguy
1
@qwertzguy, non, le -rdevrait être inoffensif puisque la -type fgarantie ne contient aucun argument, ce sont des répertoires. Les multiples open()s sont plus probablement liés aux autres fichiers ouverts par grepchaque invocation (bibliothèques, données de localisation ...) (merci pour l'édition sur ma réponse d'ailleurs)
Stéphane Chazelas
5

Si vous êtes sur un SSD et que le temps est compté, vous pouvez utiliser GNU parallel:

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

Ceci exécutera jusqu'à 8 processus grep en même temps en fonction de ce qui a été findtrouvé.

Cela écrasera un disque dur, mais un disque SSD devrait le supporter assez bien.

Naftuli Kay
la source
-1

Une dernière chose à considérer sur celui-ci est la suivante.

Est-ce que l'un des répertoires que grep devra parcourir de manière récursive contiendra plus de fichiers que le paramètre nofile de votre système ? (par exemple, nombre de descripteurs de fichiers ouverts, la valeur par défaut est 1024 sur la plupart des distributions Linux)

Si tel est le cas, alors find est le choix à faire, car certaines versions de grep génèreront une erreur de liste d'arguments trop longue quand un répertoire contenant plus de fichiers que le nombre maximal de fichiers ouverts a été défini.

Juste mon 2.

B.Kaatz
la source
1
Pourquoi grepbombarder? Au moins avec GNU grep, si vous indiquez un chemin avec une fin /et que vous l’utilisez, -Ril vous suffira de parcourir les répertoires. Le shell ne développera rien à moins que vous ne donniez des shell-globs. Ainsi, dans l'exemple donné ( /*), seul le contenu de la /matière, pas celui des sous-dossiers qui seront simplement énumérés grep, et non transmis comme argument à partir du shell.
0xC0000022L
Eh bien, considérant que le PO demandait une recherche récursive (par exemple, "grep -r -i 'le chien brun' / *"), j'ai vu le grep de GNU (au moins la version 2.9) bombarder avec: "- bash: / bin / grep: liste d'arguments trop longue "en utilisant la recherche exacte utilisée par l'OP sur un répertoire contenant plus de 140 000 sous-répertoires.
B.Kaatz