Après avoir lu les pages de manuel bash et par rapport à ce post .
J'ai encore du mal à comprendre ce que fait exactement la eval
commande et quelles seraient ses utilisations typiques. Par exemple, si nous faisons:
bash$ set -- one two three # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n} ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n) ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one
Que se passe-t-il exactement ici et comment le signe dollar et la barre oblique inverse sont-ils liés au problème?
$($n)
s'exécute$n
dans un sous-shell. Il essaie d'exécuter la commande1
qui n'existe pas.echo $1
finalement, non1
. Je ne pense pas que cela puisse être fait en utilisant des sous-shell.eval
.Réponses:
eval
prend une chaîne comme argument et l'évalue comme si vous aviez tapé cette chaîne sur une ligne de commande. (Si vous passez plusieurs arguments, ils sont d'abord joints avec des espaces entre eux.)${$n}
est une erreur de syntaxe dans bash. À l'intérieur des accolades, vous ne pouvez avoir qu'un nom de variable, avec quelques préfixes et suffixes possibles, mais vous ne pouvez pas avoir de syntaxe bash arbitraire et en particulier vous ne pouvez pas utiliser l'expansion de variable. Il y a une manière de dire «la valeur de la variable dont le nom est dans cette variable», cependant:$(…)
exécute la commande spécifiée entre parenthèses dans un sous-shell (c'est-à-dire dans un processus séparé qui hérite de tous les paramètres tels que les valeurs des variables du shell actuel), et rassemble sa sortie. Donc,echo $($n)
s'exécute$n
comme une commande shell et affiche sa sortie. Depuis$n
évalue à1
,$($n)
tente d'exécuter la commande1
, qui n'existe pas.eval echo \${$n}
exécute les paramètres passés àeval
. Après l'expansion, les paramètres sontecho
et${1}
. Alorseval echo \${$n}
lance la commandeecho ${1}
.Notez que la plupart du temps, vous devez utiliser des guillemets doubles autour des substitutions de variables et des substitutions de commandes (c'est-à-dire à chaque fois qu'il y a un
$
):"$foo", "$(foo)"
. Mettez toujours des guillemets doubles autour des substitutions de variables et de commandes , sauf si vous savez que vous devez les laisser désactivées. Sans les guillemets doubles, le shell effectue un fractionnement de champ (c'est-à-dire qu'il divise la valeur de la variable ou la sortie de la commande en mots séparés) et traite ensuite chaque mot comme un motif générique. Par exemple:eval
n'est pas utilisé très souvent. Dans certains shells, l'utilisation la plus courante est d'obtenir la valeur d'une variable dont le nom n'est pas connu avant l'exécution. Dans bash, ce n'est pas nécessaire grâce à la${!VAR}
syntaxe.eval
est toujours utile lorsque vous devez construire une commande plus longue contenant des opérateurs, des mots réservés, etc.la source
eval
reçoit une chaîne (qui peut elle-même être le résultat d'une analyse et d'une évaluation) et l'interprète comme un extrait de code.eval eval
. Je ne me souviens pas avoir jamais ressenti le besoin. Si vous avez vraiment besoin de deuxeval
passes, utilisez une variable temporaire, il sera plus facile de débogage:eval tmp="\${$i}"; eval x="\${$tmp}"
.eval
prend juste le code dans une chaîne et l'évalue selon les règles habituelles. Techniquement, ce n'est même pas correct, car il y a quelques cas secondaires dans lesquels Bash modifie l'analyse pour ne même pas effectuer d'extensions des arguments d'eval - mais c'est une information très obscure que je doute que quiconque sache.Pensez simplement à eval comme à "évaluer votre expression une fois de plus avant l'exécution"
eval echo \${$n}
devientecho $1
après le premier cycle d'évaluation. Trois changements à noter:\$
devient$
(la barre oblique inverse est nécessaire, sinon elle essaie d'évaluer${$n}
, ce qui signifie une variable nommée{$n}
, ce qui n'est pas autorisé)$n
a été évalué à1
eval
disparusAu deuxième tour, il s'agit essentiellement
echo $1
ce qui peut être directement exécuté.Alors
eval <some command>
va d'abord évaluer<some command>
(par évaluer ici, je veux dire remplacer les variables, remplacer les caractères échappés par les bons, etc.), puis exécuter à nouveau l'expression résultante.eval
est utilisé lorsque vous souhaitez créer dynamiquement des variables ou lire les sorties de programmes spécialement conçus pour être lus comme ceci. Voir http://mywiki.wooledge.org/BashFAQ/048 pour des exemples. Le lien contient également des méthodes typiques d'eval
utilisation et les risques qui y sont associés.la source
${VAR}
syntaxe est autorisée et préférée en cas d'ambiguïté (fait$VAR == $V
, suivieAR
ou$VAR == $VA
suivie deR
).${VAR}
équivaut à$VAR
. En fait, son nom de variable$n
n'est pas autorisé.eval eval echo \\\${\${$i}}
fera un triple déréférencement. Je ne sais pas s'il existe un moyen plus simple de procéder. En outre,\${$n}
fonctionne très bien (impressionsone
) sur ma machine ..echo \\\${\${$i}}
imprime\${${n}}
.eval echo \\\${\${$i}}
équivaut àecho \${${n}}`` and prints
$ {1}.
eval eval echo \\\ $ {\ $ {$ i}} `équivaut àeval echo ${1}
et imprimeone
.` escapes the second one, and the third
`échappe à l'$
après. Donc ça devient\${${n}}
après un tour d'évaluation\\
Donnant d' abord une barre oblique inverse. Puis\$
cédant un dollar. Etc.D'après mon expérience, une utilisation «typique» de eval est pour exécuter des commandes qui génèrent des commandes shell pour définir des variables d'environnement.
Peut-être avez-vous un système qui utilise une collection de variables d'environnement, et vous avez un script ou un programme qui détermine celles qui doivent être définies et leurs valeurs. Chaque fois que vous exécutez un script ou un programme, il s'exécute dans un processus fourchu, donc tout ce qu'il fait directement aux variables d'environnement est perdu lorsqu'il se termine. Mais ce script ou programme peut envoyer les commandes d'exportation à stdout.
Sans eval, vous devrez rediriger stdout vers un fichier temporaire, rechercher le fichier temporaire, puis le supprimer. Avec eval, vous pouvez simplement:
Notez que les citations sont importantes. Prenons cet exemple (artificiel):
la source
${varname}
. Je trouve pratique d'utiliser des fichiers .conf identiques sur plusieurs serveurs différents avec juste quelques éléments paramétrés par des variables d'environnement. J'ai édité / opt / lampp / xampp (qui démarre apache) pour faire ce genre d'évaluation avec un script qui fouille dans le système et génère desexport
instructions bash pour définir des variables pour les fichiers .conf.eval "$(pyenv init -)"
dans votre.bash_profile
fichier de configuration shell (ou similaire). Cela construit un petit script shell puis l'évalue dans le shell actuel.L'instruction eval indique au shell de prendre les arguments d'eval comme commande et de les exécuter via la ligne de commande. C'est utile dans une situation comme ci-dessous:
Dans votre script, si vous définissez une commande dans une variable et que vous souhaitez utiliser ultérieurement cette commande, vous devez utiliser eval:
la source
Mise à jour: certaines personnes disent qu'il ne faut jamais utiliser eval. Je ne suis pas d'accord. Je pense que le risque survient lorsque des entrées corrompues peuvent être transmises
eval
. Cependant, il existe de nombreuses situations courantes dans lesquelles ce n'est pas un risque, et il vaut donc la peine de savoir comment utiliser eval dans tous les cas. Cette réponse de stackoverflow explique les risques de eval et les alternatives à eval. En fin de compte, il appartient à l'utilisateur de déterminer si / quand eval est sûr et efficace à utiliser.Le bash
eval
instruction vous permet d'exécuter des lignes de code calculées ou acquises, par votre script bash.L'exemple le plus simple serait peut-être un programme bash qui ouvre un autre script bash sous forme de fichier texte, lit chaque ligne de texte et utilise
eval
pour les exécuter dans l'ordre. C'est essentiellement le même comportement que le bashsource
instruction , qui est celle que l'on utiliserait, à moins qu'il ne soit nécessaire d'effectuer une sorte de transformation (par exemple, filtrage ou substitution) sur le contenu du script importé.J'en ai rarement eu besoin
eval
, mais j'ai trouvé utile de lire ou d'écrire des variables dont les noms étaient contenus dans des chaînes affectées à d'autres variables. Par exemple, pour effectuer des actions sur des ensembles de variables, tout en conservant une faible empreinte de code et en évitant la redondance.eval
est conceptuellement simple. Cependant, la syntaxe stricte du langage bash et l'ordre d'analyse de l'interpréteur bash peuvent être nuancés et donner l'eval
impression d'être cryptiques et difficiles à utiliser ou à comprendre. Voici l'essentiel:L'argument passé à
eval
est une expression de chaîne calculée au moment de l'exécution.eval
exécutera le résultat analysé final de son argument comme une ligne de code réelle dans votre script.La syntaxe et l'ordre d'analyse sont stricts. Si le résultat n'est pas une ligne exécutable de code bash, dans la portée de votre script, le programme plantera sur l'
eval
instruction lorsqu'il essaiera d'exécuter des ordures.Lors du test, vous pouvez remplacer l'
eval
instruction parecho
et voir ce qui est affiché. S'il s'agit d'un code légitime dans le contexte actuel, son exécutioneval
fonctionnera.Les exemples suivants peuvent aider à clarifier le fonctionnement de eval ...
Exemple 1:
eval
l'instruction devant le code 'normal' est un NOPDans l'exemple ci-dessus, les premières
eval
instructions n'ont aucun but et peuvent être éliminées.eval
est inutile dans la première ligne car il n'y a pas d'aspect dynamique dans le code, c'est-à-dire qu'il est déjà analysé dans les dernières lignes du code bash, donc il serait identique à une instruction normale de code dans le script bash. Le 2èmeeval
est également inutile, car, bien qu'il y ait une étape d'analyse convertissant$a
en son équivalent de chaîne littérale, il n'y a pas d'indirection (par exemple, pas de référencement via la valeur de chaîne d'un nom bash réel ou d'une variable de script tenue par bash), donc il se comporterait de la même manière comme une ligne de code sans leeval
préfixe.Exemple 2:
Effectuez une affectation var en utilisant des noms var passés sous forme de valeurs de chaîne
Si vous le faisiez
echo $key=$val
, le résultat serait:Cela , étant le résultat final de l'analyse de chaîne, c'est ce qui sera exécuté par eval, d'où le résultat de l'instruction echo à la fin ...
Exemple 3:
Ajout de plus d'indirection à l'exemple 2
Ce qui précède est un peu plus compliqué que l'exemple précédent, s'appuyant davantage sur l'ordre d'analyse et les particularités de bash. La
eval
ligne serait à peu près analysée en interne dans l'ordre suivant (notez que les instructions suivantes sont du pseudocode, pas du vrai code, juste pour essayer de montrer comment l'instruction serait décomposée en étapes en interne pour arriver au résultat final) .Si l'ordre d'analyse supposé n'explique pas suffisamment ce que fait eval, le troisième exemple peut décrire l'analyse plus en détail pour aider à clarifier ce qui se passe.
Exemple 4:
Découvrez si les variables, dont les noms sont contenus dans des chaînes, contiennent elles-mêmes des valeurs de chaîne.
Dans la première itération:
Bash analyse l'argument de
eval
, eteval
voit littéralement ceci au moment de l'exécution:Le pseudo- code suivant tente d'illustrer comment bash interprète la ligne de code réel ci-dessus , pour arriver à la valeur finale exécutée par
eval
. (les lignes suivantes descriptives, pas de code bash exact):Une fois que toute l'analyse est terminée, le résultat est ce qui est exécuté, et son effet est évident, démontrant qu'il n'y a rien de particulièrement mystérieux en
eval
lui-même, et la complexité réside dans l' analyse de son argument.Le code restant dans l'exemple ci-dessus teste simplement pour voir si la valeur affectée à $ varval est nulle et, si tel est le cas, invite l'utilisateur à fournir une valeur.
la source
Au départ, je n'ai jamais appris à utiliser eval, car la plupart des gens recommanderont de rester à l'écart comme la peste. Cependant j'ai récemment découvert un cas d'utilisation qui m'a fait facepalm pour ne pas l'avoir reconnu plus tôt.
Si vous avez des tâches cron que vous souhaitez exécuter de manière interactive pour tester, vous pouvez afficher le contenu du fichier avec cat, puis copier et coller la tâche cron pour l'exécuter. Malheureusement, cela implique de toucher la souris, ce qui est un péché dans mon livre.
Disons que vous avez un travail cron à /etc/cron.d/repeatme avec le contenu:
*/10 * * * * root program arg1 arg2
Vous ne pouvez pas l'exécuter en tant que script avec tous les indésirables devant lui, mais nous pouvons utiliser cut pour se débarrasser de tous les indésirables, l'envelopper dans un sous-shell et exécuter la chaîne avec eval
eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)
La commande cut n'imprime que le 6ème champ du fichier, délimité par des espaces. Eval exécute ensuite cette commande.
J'ai utilisé un travail cron ici comme exemple, mais le concept est de formater le texte de stdout, puis d'évaluer ce texte.
Dans ce cas, l'utilisation de eval n'est pas anodine, car nous savons exactement ce que nous allons évaluer à l'avance.
la source
J'ai récemment dû utiliser
eval
pour forcer plusieurs expansions d'accolades à être évaluées dans l'ordre dont j'avais besoin. Bash fait plusieurs extensions d'accolades de gauche à droite, doncs'étend à
mais j'avais besoin de la deuxième extension d'accolade faite en premier, cédant
Le mieux que j'ai pu trouver pour faire ça a été
Cela fonctionne car les guillemets simples protègent le premier ensemble d'accolades de l'expansion lors de l'analyse de la
eval
ligne de commande, les laissant étendus par le sous-shell appelé pareval
.Il peut y avoir un schéma astucieux impliquant des extensions d'accolades imbriquées qui permet que cela se produise en une seule étape, mais s'il y en a, je suis trop vieux et stupide pour le voir.
la source
Vous avez posé des questions sur les utilisations typiques.
Une plainte courante concernant les scripts shell est que vous ne pouvez (prétendument) pas passer par référence pour récupérer des valeurs des fonctions.
Mais en fait, via "eval", vous pouvez passer par référence. L'appelé peut renvoyer une liste d'attributions de variables à évaluer par l'appelant. Il est passé par référence car l'appelant peut être autorisé à spécifier le (s) nom (s) de la ou des variables de résultat - voir l'exemple ci-dessous. Les résultats d'erreur peuvent être renvoyés par des noms standard tels que errno et errstr.
Voici un exemple de passage par référence dans bash:
La sortie ressemble à ceci:
Il y a une largeur de bande presque illimitée dans cette sortie de texte! Et il y a plus de possibilités si les multiples lignes de sortie sont utilisées: par exemple, la première ligne pourrait être utilisée pour des affectations de variables, la seconde pour un «flux de pensée» continu, mais cela dépasse le cadre de cet article.
la source
J'aime la réponse "évaluer votre expression une fois de plus avant l'exécution" et je voudrais clarifier avec un autre exemple.
Les résultats curieux de l'option 2 sont que nous aurions passé 2 paramètres comme suit:
"value
content"
En quoi cela est-il contre-intuitif? L'ajout
eval
corrigera cela.Adapté de https://stackoverflow.com/a/40646371/744133
la source
Dans la question:
génère des erreurs affirmant que les fichiers a et tty n'existent pas. J'ai compris que cela signifiait que tty n'était pas interprété avant l'exécution de grep, mais à la place que bash passait tty comme paramètre à grep, qui l'interprétait comme un nom de fichier.
Il existe également une situation de redirection imbriquée, qui devrait être gérée par des parenthèses correspondantes qui devraient spécifier un processus enfant, mais bash est primitivement un séparateur de mots, créant des paramètres à envoyer à un programme, donc les parenthèses ne sont pas mises en correspondance en premier, mais interprétées comme vu.
Je suis devenu spécifique avec grep et j'ai spécifié le fichier comme paramètre au lieu d'utiliser un tuyau. J'ai également simplifié la commande de base, en passant la sortie d'une commande sous forme de fichier, de sorte que le tuyau d'E / S ne soit pas imbriqué:
fonctionne bien.
n'est pas vraiment souhaité, mais élimine le tuyau imbriqué et fonctionne également bien.
En conclusion, bash ne semble pas aimer le pipping imbriqué. Il est important de comprendre que bash n'est pas un programme nouvelle vague écrit de manière récursive. Au lieu de cela, bash est un ancien programme 1,2,3, qui a été ajouté avec des fonctionnalités. Pour garantir la compatibilité ascendante, la manière initiale d'interprétation n'a jamais été modifiée. Si bash était réécrit pour correspondre en premier aux parenthèses, combien de bogues seraient introduits dans combien de programmes bash? De nombreux programmeurs aiment être cryptiques.
la source