123 votes

Pourquoi ne puis-je pas spécifier une variable d'environnement et la répercuter dans la même ligne de commande ?

Considérez cet extrait :

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

Ici, j'ai mis $SOMEVAR à AAA sur la première ligne - et lorsque je le répercute sur la deuxième ligne, j'obtiens le message suivant AAA comme prévu.

Mais ensuite, si j'essaie de spécifier la variable sur la même ligne de commande que l'option echo :

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... Je ne comprends pas BBB comme je m'y attendais - j'obtiens l'ancienne valeur ( AAA ).

C'est comme ça que les choses sont censées être ? Si oui, comment se fait-il que vous puissiez spécifier des variables comme LD_PRELOAD=/... program args ... et que ça marche ? Qu'est-ce qui me manque ?

118voto

Jonathan Leffler Points 299946

Ce que vous voyez est le comportement attendu. Le problème est que le shell parent évalue $SOMEVAR sur la ligne de commande avant d'invoquer la commande avec l'environnement modifié. Vous devez obtenir l'évaluation de $SOMEVAR différée jusqu'à ce que l'environnement soit défini.

Vos options immédiates comprennent :

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz .
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz' .

Les deux utilisent des guillemets simples pour empêcher le shell parent d'évaluer $SOMEVAR ; il n'est évalué qu'après avoir été placé dans l'environnement (temporairement, pour la durée de la commande unique).

Une autre option est d'utiliser la notation des sous-coquilles (comme le suggère également le document Marcus Kuhn dans son réponse ):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

La variable est définie uniquement dans le sous-shell

45voto

CodeGnome Points 25402

Le problème, revu et corrigé

Très franchement, le manuel est confus sur ce point. Le site Manuel de GNU Bash dit :

L'environnement de toute commande ou fonction simple [notez que cela exclut les buildins] peut être augmenté temporairement en le préfixant avec des affectations de paramètres, comme décrit dans Paramètres du shell. Ces instructions d'affectation n'affectent que l'environnement vu par cette commande.

Si vous analysez vraiment la phrase, ce qu'elle dit c'est que la environnement pour la commande/fonction est modifié, mais pas l'environnement pour le processus parent. Donc, cela va fonctionner :

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

car l'environnement de la commande env a été modifié avant son exécution. Cependant, cela ne fonctionnera pas :

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

en raison du moment où l'expansion des paramètres est effectuée par le shell.

Étapes de l'interprétation

Une autre partie du problème est que Bash définit ces étapes pour son interprète :

  1. Lit son entrée depuis un fichier (voir Shell scripts), depuis une chaîne de caractères fournie en tant qu'argument de l'option d'invocation -c (voir Invocation de Bash). Bash), ou depuis le terminal de l'utilisateur.
  2. Décompose l'entrée en mots et en opérateurs, en respectant les règles de citation. décrites dans Citation. Ces tokens sont séparés par des métacaractères. L'expansion des alias est effectuée par cette étape (voir Alias).
  3. Analyse les tokens en commandes simples et composées (voir Commandes Shell).
  4. Effectue les diverses expansions du shell (voir Expansions du shell), en décomposant les tokens expansés en listes de noms de fichiers (cf. Expansion), de commandes et d'arguments.
  5. Effectue toutes les redirections nécessaires (voir Redirections) et supprime les opérateurs de redirection et leurs opérandes de la liste d'arguments.
  6. Exécute la commande (voir Exécution des commandes).
  7. Optionnellement, attend que la commande se termine et collecte sa sortie. de sortie (voir État de sortie).

Ce qui se passe ici est que les buildins n'obtiennent pas leur propre environnement d'exécution, donc ils ne voient jamais l'environnement modifié. En outre, les commandes simples (par exemple, /bin/echo) faire obtient un ennvironnement modifié (c'est pourquoi l'exemple env a fonctionné) mais l'expansion de l'interpréteur de commandes a lieu dans le répertoire actuel à l'étape 4.

En d'autres termes, vous ne passez pas 'aaa $TESTVAR ccc' à /bin/echo ; vous passez la chaîne interpolée (telle qu'elle est développée dans l'environnement actuel) à /bin/echo. Dans ce cas, puisque l'environnement actuel n'a pas de code TESTVAR vous passez simplement 'aaa ccc' à la commande.

Résumé

La documentation pourrait être beaucoup plus claire. Heureusement qu'il y a Stack Overflow !

Voir aussi

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment

1 votes

J'avais déjà upvoted ceci - mais je viens juste de revenir sur cette question, et ce post contient exactement les indications dont j'ai besoin ; merci beaucoup, @CodeGnome !

0 votes

Je ne sais pas si Bash a changé dans ce domaine depuis que cette réponse a été postée, mais les affectations variables préfixées faire fonctionne maintenant avec les builtins. Par exemple, FOO=foo eval 'echo $FOO' imprime foo comme prévu. Cela signifie que vous pouvez faire des choses comme IFS="..." read ... .

0 votes

Je pense que ce qui se passe est que Bash modifie temporairement son propre environnement, et le restaure une fois la commande terminée, ce qui peut avoir des effets secondaires bizarres.

26voto

Markus Kuhn Points 161

Pour obtenir ce que vous voulez, utilisez

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

Raison :

  • Vous devez séparer l'affectation par un point-virgule ou une nouvelle ligne de la commande suivante, sinon elle n'est pas exécutée avant expansion des paramètres se produit pour la commande suivante (echo).

  • Vous devez effectuer l'affectation à l'intérieur d'un Sous-coque pour s'assurer qu'il ne persiste pas au-delà de la ligne actuelle.

Cette solution est plus courte, plus nette et plus efficace que certaines des autres solutions proposées, en particulier elle ne crée pas de nouveau processus.

3 votes

Pour les futurs googleurs qui se retrouvent ici : C'est probablement la meilleure réponse à cette question. Pour compliquer encore les choses, si vous souhaitez que l'affectation soit disponible dans l'environnement de la commande, vous devez l'exporter. Le sous-shell empêche toujours la persistance de l'affectation. (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")

1 votes

@eaj Pour exporter une variable shell vers un seul appel de programme externe, comme dans votre exemple, utilisez simplement SOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"

12voto

FatalError Points 19772

La raison en est que cela définit une variable d'environnement pour une ligne. Mais, echo ne fait pas l'expansion, bash fait. Par conséquent, votre variable est en fait développée avant l'exécution de la commande, bien que SOME_VAR es BBB dans le contexte de la commande echo.

Pour voir l'effet, vous pouvez faire quelque chose comme :

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

Ici, la variable n'est pas développée jusqu'à ce que le processus enfant s'exécute, de sorte que vous voyez la valeur mise à jour. si vous contrôlez SOME_VARIABLE à nouveau dans le shell parent, c'est toujours AAA comme prévu.

3voto

Kyros Points 375
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Utilisez un ; pour séparer les déclarations qui se trouvent sur la même ligne.

1 votes

Cela fonctionne, mais ce n'est pas tout à fait le but. L'idée est de définir l'environnement pour une seule commande, et non de manière permanente comme le fait votre solution.

0 votes

Merci pour ça @Kyros ; je ne sais pas comment j'ai pu manquer ça à ce jour :) Je me demande encore comment LD_PRELOAD et autres peuvent fonctionner devant un exécutable sans point-virgule, cependant... Merci encore.

0 votes

@JonathanLeffler - en effet, c'était l'idée ; je n'avais pas réalisé que le point-virgule rendait le changement permanent - merci de l'avoir noté !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X