web-dev-qa-db-fra.com

Comment une commande (par exemple, grep) sait-elle quand elle est exécutée dans le cadre d’une expansion globale?

Selon ma compréhension, un caractère générique global est interprété par le shell, qui exécute ensuite la commande donnée pour chaque nom de fichier correspondant. Supposons que j'ai des fichiers: abc1, abc2, and abc3 dans mon répertoire actuel. Ensuite, par exemple, echo abc* fera écho une fois pour chaque nom de fichier commençant par 'abc'.

Cependant, si je lance grep 'foo' abc*, j'imagine que cela devrait fonctionner:

grep 'foo' abc1
grep 'foo' abc2
grep 'foo' abc3

Ce qui signifie que je devrais obtenir le résultat suivant (en supposant que tous les fichiers contiennent une ligne qui dit "foo"):

foo
foo
foo

Cependant, au lieu de cela, je reçois:

abc1:foo
abc2:foo
abc3:foo

Donc, je suppose qu'il y a 2 explications possibles à cela. Premièrement, grep peut détecter qu’il a été utilisé avec des expressions globales et répond en affichant les noms de fichiers avant les correspondances. Deuxièmement, puisque vous pouvez transmettre plusieurs fichiers à grep, le shell n’exécute qu’une seule commande:

grep 'foo' abc1 abc2 abc3

Cependant, cela ne fonctionne que parce que grep accepte plusieurs fichiers à la fin. Il est possible qu'une autre commande n'autorise qu'un seul fichier à transmettre. Ainsi, si vous souhaitez exécuter la commande pour plusieurs fichiers correspondant au glob, cela ne fonctionnera pas si le parcours est effectué via la deuxième méthode décrite ci-dessus.

Quoi qu'il en soit, quelqu'un peut-il nous éclairer?

Merci!

4
Cod3Citrus

Voilà le truc: la commande ne sait pas, c'est le Shell qui fait le travail

Considérons par exemple grep 'abc' *.txt. Si nous exécutons une trace d'appels système, vous verrez quelque chose comme ceci:

bash-4.3$ strace -e trace=execve grep "abc" *.txt > /dev/null
execve("/bin/grep", ["grep", "abc", "ADDA_converters.txt", "after.txt", "altera_license.txt", "altera.txt", "ANALOG_DIGITAL_NOTES.txt", "androiddev.txt", "answer2.txt", "answer.txt", "ANSWER.txt", "ascii.txt", "askubuntu-profile.txt", "AskUbuntu_Translators.txt", "a.txt", "bash_result.txt", ...], [/* 80 vars */]) = 0
+++ exited with 0 +++

Le shell a étendu *.txt dans tous les noms de fichiers du répertoire en cours se terminant par .txt extension. Si efficacement, votre shell traduit la commande grep 'abc' *.txt en grep 'abc' file1.txt file2.txt file3.txt . . .. Ainsi, votre deuxième hypothèse est correcte.

La première hypothèse n'est pas correcte - les programmes n'ont aucun moyen de détecter glob. Il est possible de passer * en tant qu'argument de chaîne à commande, mais le travail de la commande est de décider quoi faire avec. L’expansion du nom de fichier, cependant, est la propriété de votre shell respectif, comme je l’ai déjà mentionné.

Cependant, cela ne fonctionne que parce que grep accepte plusieurs fichiers à la fin. Il est possible qu'une autre commande ne permette qu'un seul fichier à transmettre.

Exactement vrai! Les programmes ne limitent pas le nombre d'arguments de ligne de commande acceptables (par exemple, en C, un tableau de chaînes const char *args[] et en python sys.argv[]), mais ils peuvent détecter le longueur de ce tableau ou si quelque chose d'inattendu est dans la mauvaise position du tableau. grep ne fait pas cela, et accepte plusieurs fichiers, ce qui est voulu.


En passant, les citations incorrectes associées à la suppression de grep peuvent parfois poser problème. Considère ceci:

bash-4.3$ echo "one two" | strace -e trace=execve grep *est*
execve("/bin/grep", ["grep", "self_test.sh", "test.wxg"], [/* 80 vars */]) = 0
+++ exited with 1 +++

Un utilisateur non préparé s’attendrait à ce que grep corresponde à n’importe quelle ligne contenant les lettres est qu’elle contient, mais au lieu de cela, l’extension du nom de fichier de Shell a tordu. J'ai souvent vu cela se produire avec des gens qui font ps aux | grep Shell_script_name.sh, et ils s'attendent à ce que leur processus s'exécute, mais parce qu'ils ont lancé une commande depuis le même répertoire que le script , l’expansion du nom de fichier de Shell a fait en sorte que la commande grep soit complètement différente de ce que l’utilisateur attendait.

Une manière appropriée serait d'utiliser des guillemets simples:

bash-4.3$ echo "one two" | strace -e trace=execve grep '*est*'
execve("/bin/grep", ["grep", "*est*"], [/* 80 vars */]) = 0
+++ exited with 1 +++
5