Lorsque vous parcourez des fichiers, il y a deux façons:
utilisez un
for
-loop:for f in *; do echo "$f" done
utiliser
find
:find * -prune | while read f; do echo "$f" done
En supposant que ces deux boucles trouveront la même liste de fichiers, quelles sont les différences entre ces deux options en termes de performances et de manipulation?
bash
shell-script
performance
rubo77
la source
la source
find
n'ouvre pas les fichiers qu'il trouve. La seule chose que je peux voir vous mordre ici en ce qui concerne un grand nombre de fichiers est ARG_MAX .read f
les noms de fichiers seront modifiés lors de leur lecture (par exemple, les noms avec des blancs en tête).find * -prune
Semble également être une manière très compliquée de dire simplementls -1
oui?find .
, nonfind *
.ls -l
est une mauvaise idée. Mais l'analysels -1
(ce n'est1
pas unl
) n'est pas pire que l'analysefind * -prune
. Les deux échouent sur les fichiers avec des retours à la ligne dans les noms.Réponses:
1.
Le premier:
échoue pour les fichiers appelés
-n
,-e
et des variantes comme-nene
et avec quelques déploiements bash, avec les noms de fichiers contenant des barres obliques inverses.La deuxième:
échoue pour les cas encore plus (fichiers appelés
!
,-H
,-name
,(
, noms de fichiers qui commencent ou se terminent par des blancs ou contiennent des caractères ... saut de ligne)C'est le shell qui se développe
*
,find
ne fait qu'imprimer les fichiers qu'il reçoit comme arguments. Vous pourriez aussi bien avoir utilisé à laprintf '%s\n'
place ce qui, comme ilprintf
est intégré, éviterait également l' erreur potentielle de trop d'arguments .2.
L'expansion de
*
est triée, vous pouvez la rendre un peu plus rapide si vous n'avez pas besoin du tri. Danszsh
:ou simplement:
bash
n'a pas d'équivalent pour autant que je sache, vous devez donc y recourirfind
.3.
(ci-dessus en utilisant une
-print0
extension non standard GNU / BSD ).Cela implique toujours de générer une commande find et d'utiliser une
while read
boucle lente , donc ce sera probablement plus lent que d'utiliser lafor
boucle à moins que la liste des fichiers ne soit énorme.4.
De plus, contrairement à l'expansion du shell, les caractères génériques
find
effectueront unlstat
appel système sur chaque fichier, il est donc peu probable que le non-tri compense cela.Avec GNU / BSD
find
, cela peut être évité en utilisant leur-maxdepth
extension qui déclenchera une optimisation en sauvantlstat
:Parce que
find
commence la sortie des noms de fichiers dès qu'il les trouve (à l'exception de la mise en mémoire tampon de sortie stdio), où cela peut être plus rapide, c'est si ce que vous faites dans la boucle prend du temps et la liste des noms de fichiers est plus qu'un tampon stdio (4 / 8 kB). Dans ce cas, le traitement dans la boucle commencera avant d'find
avoir fini de trouver tous les fichiers. Sur les systèmes GNU et FreeBSD, vous pouvez utiliserstdbuf
pour que cela se produise plus tôt (désactivation de la mise en mémoire tampon stdio).5.
La manière POSIX / standard / portable d'exécuter des commandes pour chaque fichier avec
find
est d'utiliser le-exec
prédicat:Dans le cas de
echo
cela, c'est moins efficace que de faire une boucle dans le shell car le shell aura une version intégrée deecho
whilefind
devra générer un nouveau processus et l'exécuter/bin/echo
pour chaque fichier.Si vous devez exécuter plusieurs commandes, vous pouvez faire:
Mais attention, elle
cmd2
n'est exécutée qu'en cas decmd1
succès.6.
Une manière canonique d'exécuter des commandes complexes pour chaque fichier est d'appeler un shell avec
-exec ... {} +
:Cette fois-ci, nous sommes redevenus efficaces
echo
car noussh
utilisons celui intégré à et la-exec +
version apparaît le moinssh
possible.7.
Dans mes tests sur un répertoire avec 200.000 fichiers avec des noms courts sur ext4,
zsh
celui (paragraphe 2.) est de loin le plus rapide, suivi de la premièrefor i in *
boucle simple (bien que comme d'habitude,bash
est beaucoup plus lente que les autres shells pour cela).la source
!
commande find?!
c'est pour la négation.! -name . -prune more...
fera-prune
(etmore...
depuis-prune
retourne toujours vrai) pour chaque fichier mais.
. Donc, cela se feramore...
sur tous les fichiers de.
, mais exclura.
et ne descendra pas dans les sous-répertoires de.
. C'est donc l'équivalent standard des GNU-mindepth 1 -maxdepth 1
.J'ai essayé ceci sur un répertoire avec 2259 entrées et j'ai utilisé la
time
commande.La sortie de
time for f in *; do echo "$f"; done
(moins les fichiers!) Est:La sortie de
time find * -prune | while read f; do echo "$f"; done
(moins les fichiers!) Est:J'ai exécuté chaque commande plusieurs fois, afin d'éliminer les ratés du cache. Cela suggère de le conserver
bash
(pour i dans ...) est plus rapide que d'utiliserfind
et de canaliser la sortie (versbash
)Juste pour être complet, j'ai laissé tomber le tuyau
find
, car dans votre exemple, il est entièrement redondant. La sortie de justefind * -prune
est:Aussi,
time echo *
(la sortie n'est pas séparée par des sauts de ligne, hélas):À ce stade, je pense que la raison
echo *
est plus rapide, car il ne génère pas autant de nouvelles lignes, de sorte que la sortie ne défile pas autant. Testons ...rendements:
tandis que les
time find * -prune > /dev/null
rendements:et
time for f in *; do echo "$f"; done > /dev/null
donne:et enfin:
time echo * > /dev/null
donne:Une partie de la variation peut être expliquée par des facteurs aléatoires, mais cela semble clair:
for f in *; do ...
est plus lent quefind * -prune
, à lui seul, mais pour les constructions ci-dessus impliquant des tuyaux, est plus rapide.En outre, en passant, les deux approches semblent gérer très bien les noms avec des espaces.
MODIFIER:
Timings pour
find . -maxdepth 1 > /dev/null
vsfind * -prune > /dev/null
:time find . -maxdepth 1 > /dev/null
:find * -prune > /dev/null
:Donc, conclusion supplémentaire:
find * -prune
est plus lent quefind . -maxdepth 1
- dans le premier, le shell traite un glob, puis construit une (grande) ligne de commande pourfind
. NB:find . -prune
revient juste.
.Plus de tests
time find . -maxdepth 1 -exec echo {} \; >/dev/null
::Conclusion:
la source
find * -prune | while read f; do echo "$f"; done
a le tuyau redondant - tout ce que fait le tuyau est de produire exactement ce quifind
sort de lui-même. Sans pipe, ce serait simplement.find * -prune
La pipe n'est redondante que parce que la chose de l'autre côté de la pipe copie simplement stdin vers stdout (pour la plupart). C'est un no-op cher. Si vous voulez faire des trucs avec la sortie de find, autre que simplement le cracher à nouveau, c'est différent.*
. Comme l'a déclaré BitsOfNix : Je suggère toujours fortement de ne pas utiliser*
et.
pour à lafind
place.find . -prune
c'est plus rapide carfind
lira une entrée de répertoire textuellement, tandis que le shell fera de même, potentiellement en correspondance avec le glob (pourrait être optimisé pour*
), puis en construisant la grande ligne de commande pourfind
.find . -prune
imprime uniquement.
sur mon système. Cela ne fonctionne presque pas du tout. Ce n'est pas du tout le même quefind * -prune
celui qui montre tous les noms du répertoire courant. Un nom nuread f
modifiera les noms de fichiers avec des espaces de début.J'irais certainement avec find bien que je changerais votre trouvaille en ceci:
En termes de performances,
find
c'est beaucoup plus rapide en fonction de vos besoins bien sûr. Ce que vous avez actuellement avecfor
lui n'affichera que les fichiers / répertoires dans le répertoire courant mais pas le contenu des répertoires. Si vous utilisez find, il affichera également le contenu des sous-répertoires.Je dis trouver est mieux depuis votre
for
la*
devra être élargie d' abord et je crains que si vous avez un répertoire avec une énorme quantité de fichiers , il peut donner l'erreur liste d'arguments trop long . De même pourfind *
Par exemple, dans l'un des systèmes que j'utilise actuellement, il y a quelques répertoires avec plus de 2 millions de fichiers (<100k chacun):
la source
-prune
pour rendre les deux exemples plus semblables. et je préfère la pipe avec while alors il est plus facile d'appliquer plus de commandes dans la boucleest une utilisation inutile de
find
- Ce que vous dites est efficace "pour chaque fichier du répertoire (*
), ne trouvez aucun fichier. De plus, ce n'est pas sûr pour plusieurs raisons:-r
possibilité deread
. Ce n'est pas un problème avec lafor
boucle.for
boucle.La gestion de n'importe quel nom de fichier
find
est difficile , vous devez donc utiliser l'for
option de boucle chaque fois que possible pour cette seule raison. De plus, l'exécution d'un programme externe commefind
sera généralement plus lente que l'exécution d'une commande de boucle interne commefor
.la source
find
les-print0
ni nixargs
ne-0
sont compatibles avec POSIX, et vous ne pouvez pas mettre de commandes arbitrairessh -c ' ... '
(les guillemets simples ne peuvent pas être échappés entre guillemets simples), donc ce n'est pas si simple.Mais nous sommes des ventouses pour les questions de performances! Cette demande d'expérimentation fait au moins deux hypothèses qui la rendent très peu valable.
A. Supposons qu'ils trouvent les mêmes fichiers…
Eh bien, ils vont trouver les mêmes fichiers dans un premier temps , parce qu'ils sont tous les deux itérer sur la même glob, à savoir
*
. Maisfind * -prune | while read f
souffre de plusieurs défauts qui rendent tout à fait possible qu'il ne trouvera pas tous les fichiers que vous attendez:find
implémentations le font, mais vous ne devriez pas vous fier à cela.find *
peut se casser lorsque vous frappezARG_MAX
.for f in *
ne sera pas, carARG_MAX
s'applique àexec
, pas intégré.while read f
peut rompre avec les noms de fichiers commençant et se terminant par des espaces, qui seront supprimés. Vous pouvez surmonter cela avecwhile read
son paramètre par défautREPLY
, mais cela ne vous aidera toujours pas en ce qui concerne les noms de fichiers avec des retours à la ligne.B
echo
.. Personne ne fera cela juste pour faire écho au nom du fichier. Si vous le souhaitez, effectuez l'une des opérations suivantes:Le tuyau menant à la
while
boucle crée ici un sous-shell implicite qui se ferme à la fin de la boucle, ce qui peut ne pas être intuitif pour certains.Pour répondre à la question, voici les résultats dans un de mes répertoires contenant 184 fichiers et répertoires.
la source
$ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20811 pts/1 R+ 0:00 grep bash $ while true; do while true; do while true; do while true; do while true; do sleep 100; done; done; done; done; done ^Z [1]+ Stopped sleep 100 $ bg [1]+ sleep 100 & $ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20924 pts/1 S+ 0:00 grep bash
find *
ne fonctionnera pas correctement si*
produit des jetons qui ressemblent à des prédicats plutôt qu'à des chemins.Vous ne pouvez pas utiliser l'
--
argument habituel pour résoudre ce problème car il--
indique la fin des options et les options de find précèdent les chemins.Pour résoudre ce problème, vous pouvez utiliser à la
find ./*
place. Mais cela ne produit pas exactement les mêmes chaînes quefor x in *
.Notez que
find ./* -prune | while read f ..
n'utilise pas réellement la fonctionnalité de numérisation defind
. C'est la syntaxe de globbing./*
qui traverse réellement le répertoire et génère des noms. Ensuite, lefind
programme devra effectuer au moins unestat
vérification sur chacun de ces noms. Vous avez la charge de lancer le programme et de lui faire accéder à ces fichiers, puis de faire des E / S pour lire sa sortie.Il est difficile d'imaginer comment cela pourrait être tout sauf moins efficace que
for x in ./* ...
.la source
Bien pour commencer,
for
un mot-clé shell, intégré à Bash,find
est un exécutable distinct.La
for
boucle ne trouvera les fichiers du caractère globstar que lorsqu'elle se développe, elle ne récursive pas dans les répertoires qu'elle trouve.Find, d'autre part, recevra également une liste développée par le globstar, mais il trouvera récursivement tous les fichiers et répertoires sous cette liste développée et les dirigera chacun vers la
while
boucle.Ces deux approches peuvent être considérées comme dangereuses dans le sens où elles ne gèrent pas les chemins ou les noms de fichiers contenant des espaces.
C'est à peu près tout ce que je peux penser de commenter ces 2 approches.
la source
Si tous les fichiers retournés par find peuvent être traités par une seule commande (évidemment non applicable à votre exemple d'écho ci-dessus), vous pouvez utiliser xargs:
la source
Depuis des années, j'utilise ceci: -
pour rechercher certains fichiers (par exemple * .txt) qui contiennent un modèle que grep peut rechercher et le diriger vers plus afin qu'il ne défile pas hors de l'écran. Parfois j'utilise le >> pipe pour écrire les résultats dans un autre fichier que je pourrai regarder plus tard.
Voici un échantillon du résultat: -
la source