web-dev-qa-db-fra.com

Bash: spécifier les variables d'environnement pour l'écho sur la 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 quand je le fais écho sur la deuxième ligne, j'obtiens le contenu AAA comme prévu.

Mais alors, si j'essaye de spécifier la variable sur la même ligne de commande que le echo:

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

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

Est-ce ainsi que les choses devraient être? Si oui, comment se fait-il que vous puissiez alors spécifier des variables comme LD_PRELOAD=/... program args ... et ça marche? Qu'est-ce que je rate?

80
sdaau

Ce que vous voyez, c'est le comportement attendu. Le problème est que le shell parent évalue $SOMEVAR sur la ligne de commande avant d'appeler la commande avec l'environnement modifié. Vous devez obtenir l'évaluation de $SOMEVAR différé 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'.

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

Une autre option consiste à utiliser la notation sous-Shell (comme le suggère également Marcus Kuhn dans son réponse ):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

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

88
Jonathan Leffler

Le problème, revisité

Franchement, le manuel prête à confusion sur ce point. Le manuel GNU Bash dit:

L'environnement de toute commande ou fonction simple [notez que cela exclut les prédéfinis] 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 affectent uniquement l'environnement vu par cette commande.

Si vous analysez vraiment la phrase, cela signifie que l'environnement de la commande/fonction est modifié, mais pas l'environnement du processus parent. Donc, cela fonctionnera:

$ 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 d'interprétation

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

  1. Lit son entrée dans un fichier (voir Scripts Shell), dans une chaîne fournie en argument à l'option d'invocation -c (voir Invocation de Bash) ou dans 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 Citations. Ces jetons sont séparés par des métacaractères. L'expansion des alias est effectuée par cette étape (voir Alias).
  3. Analyse les jetons en commandes simples et composées (voir Commandes Shell).
  4. Effectue les différentes extensions Shell (voir Expansions Shell), divisant les jetons étendus en listes de noms de fichiers (voir Extension de nom de fichier) et commandes et arguments.
  5. Effectue toutes les redirections nécessaires (voir Redirection) et supprime les opérateurs de redirection et leurs opérandes de la liste des arguments.
  6. Exécute la commande (voir Exécution de commandes).
  7. Attend éventuellement la fin de la commande et collecte son état de sortie (voir État de sortie).

Ce qui se passe ici, c'est que les builtins n'ont pas leur propre environnement d'exécution, donc ils ne voient jamais l'environnement modifié. De plus, les commandes simples (par exemple/bin/echo) obtiennent un environnement modifié (c'est pourquoi l'exemple env a fonctionné) mais l'expansion du Shell prend placer dans l'environnement 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 que développée dans l'environnement actuel) à/bin/echo. Dans ce cas, étant donné que l'environnement actuel n'a pas [~ # ~] testvar [~ # ~] , vous passez simplement 'aaa ccc' au commander.

Sommaire

La documentation pourrait être beaucoup plus claire. Heureusement qu'il y a débordement de pile!

Voir également

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

33
Todd A. Jacobs

Pour réaliser 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 (écho).

  • Vous devez effectuer l'affectation dans un environnement subshell, pour vous assurer qu'elle ne persiste pas au-delà de la ligne actuelle.

Cette solution est plus courte, plus soignée et plus efficace que certaines autres suggérées, en particulier elle ne crée pas de nouveau processus.

19
Markus Kuhn

La raison en est que cela définit une variable d'environnement pour une ligne. Mais, echo ne fait pas l'expansion, bash le fait. Par conséquent, votre variable est réellement développée avant l'exécution de la commande, même si SOME_VAR est 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, vous voyez donc la valeur mise à jour. si vous cochez SOME_VARIABLE à nouveau dans le shell parent, c'est toujours AAA, comme prévu.

10
FatalError

Voici une alternative:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz
1
brian
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Utiliser un ; pour séparer les instructions qui sont sur la même ligne.

1
Kyros