Pourquoi «A = 10 echo $ A» n'imprime-t-il pas 10?

25

Cette commande:

A=10 echo $A

imprime une ligne vide. Pourquoi ne pas 10? Pourquoi le paramètre d'environnement temporaire sur place ne fonctionne-t-il pas?

Je veux connaître la raison et l'explication plutôt que la solution.

j'ai utilisé

LANG=C gcc ...

pour forcer gcc, utilisez la langue de secours (anglais) au lieu de la langue système (chinois). Je suppose donc qu'un VAR=valuepréfixe configurera un environnement temporaire pour la commande qui le suit. Mais il semble que j'ai un malentendu.

Earth Engine
la source

Réponses:

22

C'est une question d'ordre dans lequel se déroulent les différentes étapes de l'évaluation d'une commande.

A=10 echo $Aanalyse d'abord la commande en une commande simple composée de trois mots A=10, echoet $A. Ensuite, chaque mot subit une substitution de variable, c'est-à-dire la transformation d'expansions de variables telles que $Adans leurs valeurs (j'omets des étapes qui ne font rien de visible).

Si Aa la valeur fooinitialement, le résultat des étapes d'expansion est une commande simple qui a encore trois mots: A=10, echoet foo. (Le shell se souvient également à ce stade quels caractères étaient initialement entre guillemets - dans ce cas, aucun.) L'étape suivante consiste à exécuter la commande. Puisqu'il A=10commence par un nom de variable valide suivi d'un signe égal, il est traité comme une affectation; la variable Aest définie à la 10fois dans le shell et dans l'environnement lors de l'exécution de la commande. (Normalement, vous devez écrire export Apour avoir Adans l'environnement et pas seulement comme variable shell; c'est une exception.) Le mot suivant n'est pas une affectation, il est donc traité comme un nom de commande (c'est une commande intégrée). leechoLa commande ne dépend d'aucune variable, a donc A=10 echo $Aexactement le même effet que echo $A.

Si vous souhaitez définir une variable pour la durée d'une commande uniquement, mais en tenant compte de l'affectation lors de l'exécution de la commande, vous pouvez utiliser un sous-shell. Un sous-shell, indiqué par des parenthèses, rend tous les changements d'état (affectations de variables, répertoire courant, définitions de fonction, etc.) locaux au sous-shell.

(A=10; echo $A)

Faites cela export A=10si vous voulez exporter la variable dans l'environnement afin qu'elle soit vue par les programmes externes.

Gilles 'SO- arrête d'être méchant'
la source
Merci, puis-je dire A=10 (echo $A)et obtenir 10?
Earth Engine
2
@EarthEngine Non, ce serait une erreur de syntaxe. L'affectation doit être au début d'une commande simple (c'est-à-dire juste un nom de commande et certains paramètres, et éventuellement des affectations initiales et des redirections). A=10; (echo $A)sorties 10mais définit également Apour le reste du script.
Gilles 'SO- arrête d'être méchant'
2
@EarthEngine Mais vous pouvez le dire A=10 eval 'echo $A'. Les guillemets simples ne $Asont plus interprétés tant que la ligne entière n'est pas évaluée ... A quel moment A = 10. Je considère cette réponse plus juste que l'acceptée.
Oli
Je pense que c'est la bonne explication. La raison du comportement est simplement l'ordre du moment où l'expansion $Aet l'affectation de Ase produisent. Par exemple, ne A=5; A=6 let 'a=A'; echo $aretourne 6pas 5et je ne pense pas que letdémarre un sous-shell, car il s'agit d'une commande intégrée.
David Ongaro
@EarthEngine: Quand on dit que la bonne explication est l' ordre d'évaluation, cela peut être trompeur: neA=10 echo $A sera pas défini A=10pour les commandes par la suite, même si elles sont sur des lignes différentes (quand clairement l'affectation a déjà été évaluée). Ce n'est pas une question d'ordre, c'est une question de portée
MestreLion
37

Lorsque vous utilisez LANG=C gcc ... ce qui se passe, c'est que le shell définit LANG pour gccl'environnement uniquement , et non pour l' environnement actuel lui-même ( voir note ). Ainsi, une fois gccterminé, LANGrevient à sa valeur précédente (ou non définie).

De plus, lorsque vous l'utilisez, A=10 echo $Ac'est le shell qui remplace $ A, pas echo, et cette substitution (appelée "expansion") se produit avant que l'instruction ne soit évaluée (y compris l'affectation), donc pour fonctionner comme prévu, Ala valeur de doit être déjà définie dans l' environnement actuel avant cette déclaration.

C'est pourquoi A=10 echo $Ane fonctionne pas comme prévu: A=10sera défini pour l'écho, mais l'écho ignore en interne la valeur de la variable d'environnement A. Et $Aest remplacé par la valeur définie dans le shell actuel (qui n'est aucun), puis passé en argument à echo.

Donc , votre hypothèse est correcte: VAR=value command ne travail, mais cela est pertinent que si l' commandinterne utilise VAR. Sinon, vous pouvez toujours passer valuecomme argument à command, mais les arguments sont remplacés par le shell actuel , ils doivent donc être définis avant utilisation:VAR=value; command "$VAR"

Si vous savez comment créer un script exécutable, vous pouvez essayer ceci comme test:

#!/bin/sh
echo "1st argument is $1"
echo "A is $A"

Enregistrez-le sous testscriptet essayez:

$ A=5; A=10 testscript "$A"; echo "$A"
1st argument is 5
A is 10
5

Enfin, il vaut la peine de connaître la différence entre les variables shell et d' environnement et les arguments de programme .

Voici quelques bonnes références:

.

(*) Note: techniquement la coquille ne trouve dans l' environnement actuel aussi, et voici pourquoi: Certaines commandes, comme echo, readet testsont builtins shell , et en tant que tels , ils ne fraient pas un processus enfant. Ils fonctionnent dans l'environnement actuel. Mais le shell prend soin que l'affectation ne dure que jusqu'à l'exécution de la commande, donc à toutes fins pratiques, l'effet est le même: l'affectation n'est vue que par cette seule commande.

MestreLion
la source
2
Cette explication est en fait incorrecte, même si elle aboutit à la conclusion correcte dans tous les cas, sauf quelques-uns. La vraie explication est l'ordre d'expansion: $Aest évalué avant que l'affectation n'ait lieu. Je pense que votre explication échoue uniquement dans le cas des utilitaires intégrés réguliers dont le comportement dépend de la valeur de la variable: le intégré voit la valeur attribuée. Un exemple courant est celui IFS=: read one two three restqui lit les champs séparés par deux-points: la fonction readintégrée voit la valeur de IFS.
Gilles 'SO- arrête d'être méchant'
«Pas pour le shell actuel lui-même» est faux: la variable est définie dans le shell actuel, mais elle ne dure que pour la commande simple actuelle. echoverrait la valeur 10pour A, si cela importait.
Gilles 'SO- arrête d'être méchant'
@Gilles: merci beaucoup pour la clarification! Je n'étais pas au courant de cette subtilité. Donc, si j'ai bien compris, bash doit être défini pour l' environnement actuel , sinon les commandes internes (qui n'en engendrent pas de nouveau pid) ne verraient pas l'affectation comme les autres commandes "regurlar". Mais il se désactive une fois la commande exécutée pour limiter la portée de l'affectation. Si c'est correct, je vais corriger ma réponse en conséquence. PS: les détails techniques mis à part, je pense toujours qu'une réponse devrait mettre l'accent sur l'aspect de la portée, pas sur l'ordre d'évaluation, sinon on pourrait penser A=10 test; echo $Aimprimer 10
MestreLion
3

Une façon peut-être propre de faire ce que vous désirez apparemment est d'émettre la commande:

A=10 eval 'echo $A'

Ce qui en fait reportera la substitution de la valeur 10 à la place de $ A dans un contexte ultérieur (c'est-à-dire «à l'intérieur» de l'évaluation, qui connaît déjà l'affectation). Notez que les guillemets simples sont essentiels. Une telle construction communique proprement l'affectation à la commande souhaitée (écho dans ce cas) sans courir le risque de polluer votre environnement actuel.

nhokka
la source