Pourquoi ne pas étendre cette variable lorsque je préfixe une command avec une "affectation de variable unique"

Si j'exécute cette command bash et que je préfixe l'instruction, de sorte que le fruit la variable doit exister, mais seulement pour la durée de cette command:

 $ fruit=apple echo $fruit $ 

Le résultat est une ligne vide. Pourquoi?

Pour citer un commentaire de wildcard sur cette question :

l'expansion des parameters est effectuée par le shell, et la variable "fruit" n'est pas une variable shell; c'est seulement une variable d'environnement dans l'environnement de la command "echo"

Une variable d'environnement est toujours une variable, donc sûrement cela devrait toujours être disponible pour la command echo?

Pour bien comprendre cela, commençons par différencier les variables shell des variables d' environnement.

Les variables d'environnement sont une propriété de TOUS les process, qu'ils les utilisent en interne ou non. Même sleep 10 quand il est en cours d'exécution a des variables d'environnement. De même que tous les process ont un PID (identificateur de process), un directory de travail courant (cwd), un PPID (parent PID), une list d'arguments (même si elle est vide), etc. De même, tous les process ont ce qu'on appelle un «environnement», qui est hérité du process parent quand il fourche.

Du sharepoint vue de l'auteur de l'utilitaire (quelqu'un qui écrit du code en C), les process ont la possibilité de définir, d'annuler ou de modifier les variables d'environnement. Cependant, du sharepoint vue d'un auteur de script, la plupart des outils n'offrent pas cette fonctionnalité à leurs users. Au lieu de cela, vous utilisez votre shell pour modifier l'environnement de process qui est ensuite hérité lorsque la command (binary externe) que vous avez appelée est exécutée. (L'environnement de votre shell lui-même peut être modifié et la modification héritée, ou vous pouvez diriger votre shell pour effectuer la modification après le forçage mais avant d'exécuter la command que vous avez appelée. approches.)

Les variables shell sont une autre chose. Bien qu'à l' intérieur de la coquille ils se comportent de la même manière, la différence est que de simples "variables shell" ne modifient pas ou n'influencent pas le comportement des commands que vous appelez de votre shell. Dans une terminologie appropriée, la distinction serait en fait énoncée un peu différemment; les variables shell exscopes feront partie de l'environnement des outils que vous appelez, alors que les variables shell qui ne sont pas exscopes ne le seront pas. Cependant, il me semble plus utile que la communication fasse reference à des variables shell qui ne sont pas exscopes en tant que «variables shell» et aux variables shell qui sont exscopes en tant que «variables d'environnement» parce que ce sont des variables d'environnement du sharepoint vue des process.


C'est beaucoup de text. Regardons quelques exemples et décrivons ce qui se passe:

 $ somevar=myfile $ ls -l "$somevar" -rw-r--r-- 1 Myname staff 0 May 29 19:12 myfile $ 

Dans cet exemple, somevar est juste une variable shell, rien de spécial à ce sujet. L' expansion du paramètre shell (voir LESS='+/Parameter Expansion' man bash ) se produit avant que l'exécutable ls soit réellement chargé ("exec" ed) et la command ls (process) ne voit jamais la string "dollar sign somevar". Il ne voit que la string "monfile", interprète cela comme le path vers un file dans le directory de travail courant, et obtient et imprime des informations à ce sujet.

Si nous export somevar avant la command ls , le fait que somevar=myfile apparaisse dans l' environnement du process ls , mais cela n'affectera rien car la command ls ne fera rien avec cette variable. Pour voir l' effet d'une variable d'environnement, nous devons choisir une variable d'environnement que le process que nous appelons vérifiera et fera quelque chose avec.


bc: Calculasortingce de base

Il y a peut-être un meilleur exemple, mais c'est celui que j'ai trouvé ce n'est pas trop compliqué. D'abord, vous devez savoir que bc est une calculasortingce de base, et traite et calcule des expressions mathématiques. (Après avoir traité le contenu de tous les files d'input, il traite son input standard.Je ne vais pas utiliser son input standard pour mes exemples, je vais juste appuyer sur Ctrl-D, qui ne montrera pas dans les extraits de text ci-dessous. J'utilise -q pour supprimer un message d'introduction sur chaque invocation.)

La variable d'environnement que je vais illustrer est décrite dans man bc :

  BC_ENV_ARGS This is another mechanism to get arguments to bc. The format is the same as the command line arguments. These arguments are processed first, so any files listd in the environment argu- ments are processed before any command line argument files. This allows the user to set up "standard" options and files to be processed at every invocation of bc. The files in the envi- ronment variables would typically contain function definitions for functions the user wants defined every time bc is run. 

Voici:

 $ cat file1 5*5 $ bc -q file1 25 $ cat file2 6*7 8+9+10 $ bc -q file2 42 27 $ bc -q file1 file2 25 42 27 $ 

C'est juste pour montrer comment fonctionne bc . Dans chacun de ces cas, j'ai dû appuyer sur Ctrl-D pour signaler "fin d'input" à bc .

Passons maintenant une variable d'environnement directement à bc :

 $ BC_ENV_ARGS=file1 bc -q file2 25 42 27 $ echo "$BC_ENV_ARGS" $ bc -q file2 42 27 $ 

Notez que ce que nous mettons dans cette variable n'est pas visible plus tard à partir d'une command echo . En plaçant l'assignation dans la même command (sans point-virgule non plus), nous avons placé cette assignation de variable dans l'environnement de bc -it, laissant le shell lui-même non affecté.

BC_ENV_ARGS maintenant BC_ENV_ARGS comme variable shell :

 $ BC_ENV_ARGS=file1 $ echo "$BC_ENV_ARGS" file1 $ bc -q file2 42 27 $ 

Ici vous pouvez voir que notre command echo peut voir le contenu, mais cela ne fait pas partie de l'environnement de bc donc bc ne peut rien faire de spécial.

Bien sûr, si nous mettons la variable elle-même dans la list des arguments de BC, nous verrons quelque chose:

 $ bc -q "$BC_ENV_ARGS" 25 $ 

Mais ici, c'est le shell qui étend la variable, puis file1 est ce qui apparaît dans la list des arguments de bc . Donc, il l'utilise toujours comme une variable shell, pas une variable d'environnement.

Maintenant, exportons cette variable, donc c'est à la fois une variable shell ET une variable d'environnement:

 $ export BC_ENV_ARGS $ echo "$BC_ENV_ARGS" file1 $ bc -q file2 25 42 27 $ 

Et ici vous pouvez voir que file1 est traité avant le file2 , même s'il n'est pas mentionné sur la command line ici. Cela fait partie de l'environnement du shell et devient partie intégrante de l'environnement de bc lors de l'exécution de ce process, de sorte que la valeur de cette variable d'environnement est héritée et affecte le fonctionnement de bc .

Nous pouvons toujours substituer ceci par command, même le substituer à une valeur vide:

 $ BC_ENV_ARGS= bc -q file2 42 27 $ echo "$BC_ENV_ARGS" file1 $ bc -q file2 25 42 27 $ 

Mais comme vous pouvez le voir, la variable rest définie et exscope dans notre shell, visible à la fois pour le shell lui-même et pour les commands bc ultérieures qui ne surchargent pas la valeur. Il restra comme cela à less que nous ne «l'expulsions» ou «l'arrêtons». Je vais faire ce dernier:

 $ unset BC_ENV_ARGS $ echo "$BC_ENV_ARGS" $ bc -q file2 42 27 $ 

Un autre exemple, impliquant la reproduction d'un autre shell:

Tapez les commands suivantes les unes après les autres dans votre shell et considérez les résultats. Voyez si vous pouvez prédire les résultats avant de les exécuter.

 # fruit is not set echo "$fruit" sh -c 'echo "$fruit"' # fruit is set as a shell variable in the current shell only fruit=apple echo "$fruit" sh -c 'echo "$fruit"' sh -c "echo $fruit" ### NOT advised for use in scripts, for illustration only # fruit is exported, so it's accessible in current AND new processes export fruit echo "$fruit" sh -c 'echo "$fruit"' echo '$fruit' ### I threw this in to make sure you're not confused on quoting # fruit is unset again unset fruit echo "$fruit" sh -c 'echo "$fruit"' # setting fruit directly in environment of single command but NOT in current shell fruit=apple sh -c 'echo "$fruit"' echo "$fruit" fruit=apple echo "$fruit" # showing current shell is unaffected by directly setting env of single command fruit=cherry echo "$fruit" fruit=apricot sh -c 'echo "$fruit"' echo "$fruit" sh -c 'echo "$fruit"' 

Et un dernier pour plus de difficulté: Pouvez-vous prédire la sortie des commands suivantes exécutées en séquence? 🙂

 fruit=banana fruit=orange sh -c 'fruit=lemon echo "$fruit"; echo "$fruit"; export fruit=peach' echo "$fruit" 

Veuillez mentionner dans les commentaires toutes les clarifications souhaitées; Je suis sûr que cela pourrait en utiliser quelques-uns. Mais cela devrait nous aider, même si c'est le cas.

Le problème est que le shell actuel étend la variable trop tôt; il n'est pas mis dans son context donc la command echo ne reçoit aucun argument, c'est à dire que les commands finissent par être:

 $ fruit=apple echo 

Voici une solution de contournement où la variable ne se développe pas trop tôt en raison des guillemets simples:

 $ fruit=apple sh -c 'echo $fruit' 

Vous pouvez également utiliser un script shell d'une ligne qui montre que la variable fruit est correctement transmise à la command exécutée:

 $ cat /tmp/echof echo $fruit $ /tmp/echof $ fruit=apple /tmp/echof apple $ echo $fruit $ 

Quelques commentaires comme cette question a suscité des controverses et des discussions inattendues:

  • Le fait que le fruit variable soit déjà exporté ou non n'affecte pas le comportement, ce qui count, c'est ce que la valeur de la variable est au moment précis où le shell l'étend.
  $ fruit d'export = banane 
  $ fruit = pomme echo $ fruit 
  banane 
 
  • Le fait que la command echo soit embeddede n'affecte pas le problème OP. Cependant, il existe des cas où l'utilisation de fonctions internes ou shell avec cette syntaxe a des effets secondaires inattendus, par exemple:
  $ fruit d'export = banane 
  $ fruit = pomme eval 'echo $ fruit' 
  Pomme 
  $ echo $ fruit 
  Pomme 
  • Bien qu'il y ait une similitude entre la question posée ici et celle-là, ce n'est pas exactement le même problème. Avec cette autre question, la valeur temporaire de la variable IFS n'est pas encore disponible lorsque le mot shell sépare une autre variable $var alors que la valeur temporaire de fruit variable fruit n'est pas encore disponible lorsque le shell développe la même variable.

  • Il y a aussi cette autre question où le PO pose la question de la signification de la syntaxe utilisée et plus précisément se request «pourquoi cela fonctionne-t-il? Ici, le PO est conscient de la signification mais signale un comportement inattendu et s'interroge sur sa cause, à savoir «pourquoi cela ne marche-t-il pas? Ok, après avoir lu de plus près la mauvaise capture d'écran postée sur l'autre question, la même situation y est en effet décrite ( BAZ=jake echo $BAZ ) alors oui, après tout c'est un doublon

Parce que les expansions sur la command line ont lieu avant les assignations de variables. La norme le dit ainsi :

Lorsqu'une command simple donnée doit être exécutée, les opérations suivantes doivent être exécutées:

  1. Les mots qui sont reconnus comme des affectations variables […] sont sauvegardés pour le traitement aux étapes 3 et 4.

  2. Les mots qui ne sont pas des assignations variables ou des redirections doivent être développés. […]

  3. Les redirections doivent être effectuées comme décrit dans Redirection.

  4. Chaque assignation de variable doit être étendue […] avant d'assigner la valeur.

Notez l'ordre: dans la première étape, les affectations ne sont sauvegardées que , les autres mots sont étendus, et à la fin seulement les affectations de variables ont lieu.

Bien sûr, il est très probable que la norme le dit seulement parce que ça a toujours été comme ça, et ils ont simplement codifié le comportement existant. Il ne dit rien sur l'histoire ou le raisonnement derrière. Le shell doit identifier les mots qui ressemblent à des affectations à un certain point (pour ne pas les prendre dans la command), donc je suppose qu'il pourrait fonctionner dans l'autre ordre, assigner les variables d'abord, puis étendre n'importe quoi sur la command line . (ou juste le faire de gauche à droite …)