Comment un shell (bash, par exemple) élargit-il les modèles génériques?

9

Supposons qu'un répertoire contient 100 fichiers commençant par la lettre «a».

Si je fais un grep <some string> a*depuis le terminal, comment le shell va-t-il gérer cela?

Va-t-il développer l'expression régulière, obtenir une liste de tous les fichiers commençant par a et grep sur chacun de ceux-ci de manière séquentielle? Ou existe-t-il un autre moyen?

Supposons que j'ai un tableau des noms de fichiers ci-dessus qui commencent par «a». Cela prendra-t-il plus / moins de temps si j'écris une boucle for et fais l'itération moi-même dans un script shell ou un programme ac?

harithski
la source
7
BTW, ce n'est globpas une expression régulière. Grande différence.
Aaron D. Marasco

Réponses:

8

Tout d'abord, un nitpick: une chaîne comme a*dans la syntaxe shell normale est un glob, qui fonctionne différemment des expressions régulières.

Sur une vue d'ensemble de haut niveau, l'interpréteur de shell (c'est-à-dire bash) développe la chaîne a*en une liste de chaque nom de fichier correspondant au modèle a*. Celles-ci font alors partie des paramètres de ligne de commande d'une seule instance de grep(pour les programmeurs, tous les mots développés vont comme des chaînes distinctes dans l' argvargument de main). Cette grepcommande unique analyse ensuite les arguments de la manière qu'elle choisit, et c'est grepà interpréter ces arguments comme des noms de fichiers, des options, des arguments d'options, des expressions régulières, etc., et de prendre les actions appropriées. Tout se passe séquentiellement (aucune grepimplémentation AFAIK n'utilise plusieurs threads).

Si vous implémentez une boucle dans un script shell pour faire la même chose, il est presque garanti d'être plus lent que le processus ci-dessus, pour les raisons suivantes. Si vous générez un nouveau processus grep pour chaque fichier, il sera très certainement plus lent en raison de la multiplication inutile des frais généraux de création de processus. Si vous avez construit vous-même la liste des arguments dans le script shell et utilisé une seule instance de grep, tout ce que vous faites dans le shell sera encore plus lent car les commandes shell doivent être interprétées (par bash), ce qui ajoute une couche supplémentaire de code, et vous aurez juste réimplémenter ce que bash faisait déjà plus rapidement en interne dans le code compilé.

Quant à l'écrire vous-même en C, vous pouvez probablement facilement obtenir des performances comparables au processus décrit dans le premier paragraphe, mais il est peu probable que vous puissiez obtenir un gain de performances suffisant par rapport aux implémentations grep / bash actuelles pour justifier le temps dépensé sans plonger dans les optimisations de performances spécifiques à la machine ou sacrifier la portabilité. Vous pourriez peut-être essayer de proposer une version arbitrairement parallélisable de grep, mais même cela peut ne pas aider car vous êtes plus susceptible d'être lié aux E / S que lié au CPU. L'expansion des globes et grep sont déjà "assez rapides" pour la plupart des utilisations "normales".

jw013
la source
Merci pour la réponse très détaillée. En fait, j'ai besoin de grep des fichiers compressés (quelques Go chacun). J'ai une liste de ces fichiers. J'ai maintenant le choix entre construire une expression régulière (compliquée) pour correspondre à ces fichiers ou parcourir la liste connue et exécuter grep sur chacun d'entre eux (facile). D'où le souci des performances.
harithski
essayez zcatet zgrep; pas besoin de les décompresser un par un
jw013
Oui bien sûr. J'utilise zgrep.
harithski
6

Oui, il se développera en une liste de fichiers et transmettra la liste résultante au grepprogramme. C'est du moins ce qui est man bashdit dans la sous-section Expansion du nom de chemin .

Il existe une autre façon d'utiliser l'expansion dans des cas simples comme vous le mentionnez: écrivez grep <some_string> aet avant d'appuyer sur* , appuyez sur ESC. Cela étendra la liste des fichiers correspondants directement dans la ligne de commande, afin que vous puissiez vérifier que la liste est correcte avant d'appuyer sur Enter.

Quant à la deuxième partie de votre question, cela dépend. Si vous voulez écrire une boucle for qui exécute tour à tour grep sur chacun des fichiers, alors ce serait certainement plus lent, car le programme grep ne sera pas exécuté une fois, mais une fois par fichier. Cependant, il est important de garder à l'esprit qu'il existe une certaine limite à la longueur étendue des arguments de ligne de commande que vous pouvez utiliser, bien qu'elle soit généralement assez élevée. Pour voir cela, vous pouvez essayer grep adasdsadf /usr/*/*/* >/dev/null.

rozcietrzewiacz
la source
2
ESC+*n'est pas exactement la même chose que de laisser bash se développer * car ESC+*il insérera des fichiers dot (noms commençant par a .) alors que l'expansion de *dépend du dotglob shoptparamètre. La séquence de touches pour développer et insérer des globes est C-x *par défaut et correspond à la commande readline glob-expand-word.
jw013
1
@ jw013 Merci pour l'information! Cela ne semble pas changer le cas de l' a*expansion, mais est certainement important à plus grande échelle.
rozcietrzewiacz
2
zshRemarque: il suffit de frapper la touche de tabulation sur les paramètres extensibles (motifs globaux, expansion d'accolade, substitution de commande,…) pour les développer.
Stéphane Gimenez
@ jw013 En fait, je viens de tester le C-xraccourci et il n'élargit pas la liste des fichiers sur mon système (en utilisant bash).
rozcietrzewiacz
1
@roz Right - Je ne l'utilise presque jamais de toute façon, je voulais juste souligner la différence (plutôt nitpicky) :). C-x *ne fait que des globes qui ne font que des noms de fichiers, mais Esc *en fait beaucoup plus car c'est le cas insert-completions, comme dans tous les compléments possibles. Cela signifie que l'utilisation Esc *sur une ligne de commande vide insérera le nom de chaque fichier exécutable unique dans votre $PATH, par exemple.
jw013