Pourquoi la définition d'une variable avant une commande est-elle légale dans bash?

68

Je viens de rencontrer plusieurs réponses, telles que l' analyse d'un fichier texte délimité ... qui utilise la construction:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

où la IFSvariable est définie avant la readcommande.

J'ai lu la référence bash mais je ne peux pas comprendre pourquoi c'est légal.

j'ai essayé

$ x="once upon" y="a time" echo $x $y

à partir de l'invite de commande bash mais rien ne se fait écho. Quelqu'un peut-il m'indiquer où cette syntaxe est définie dans la référence qui permet à la variable IFS d'être définie de cette manière? Est-ce un cas particulier ou puis-je faire quelque chose de similaire avec d'autres variables?

Mike Lippert
la source
Voir aussi Quand puis-je utiliser un IFS temporaire pour la division de champs?
Gilles 'SO- arrête d'être méchant'

Réponses:

69

C'est sous Shell Grammar, Simple Commands (emphase ajoutée):

Une commande simple est une séquence d'affectations de variables facultatives suivies de mots et de redirections séparés par des blancs et terminées par un opérateur de contrôle. Le premier mot spécifie la commande à exécuter et est passé comme argument zéro. Les mots restants sont passés en tant qu'arguments à la commande appelée.

Ainsi, vous pouvez passer n'importe quelle variable de votre choix. Votre echoexemple ne fonctionne pas car les variables sont transmises à la commande et ne sont pas définies dans le shell. Le shell se développe $xet $y avant d' appeler la commande. Cela fonctionne, par exemple:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time
derobert
la source
Merci! J'ai fait beaucoup de recherches sur Google avant de demander, et essayais de comprendre où il est dit que dans la référence, w / no chance. J'essaie de mieux écrire les scripts bash et votre réponse aide.
Mike Lippert
1
Hmm, je pense que je dois trouver une meilleure référence, le mien (voir lien ci-dessus) ne dit pas qu'il n'a pas de section Shell Grammar.
Mike Lippert
1
@MikeLippert Voir 3.7.4 dans cette référence ("L'environnement de toute commande ou fonction simple peut être augmenté temporairement en le préfixant avec des affectations de paramètres"). Je pense que cette référence provient d'une ancienne version de bash. Je viens de courir man bashsur mon système ...
derobert
29

Les variables définies deviennent des variables d’environnement sur le processus forké.

Si tu cours

A="b" echo $A

puis bash se développe d'abord $Adans ""puis s'exécute

A="b" echo

Voici le bon chemin:

x="once upon" y="a time" bash -c 'echo $x $y'

Notez les guillemets simples dans bash -c, sinon vous avez le même problème que ci-dessus.

Ainsi, votre exemple de boucle est légal car la commande 'read' intégrée dans bash va rechercher IFS dans ses variables d’environnement et trouver ,. Donc,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

imprimera TEST is and I is test

Enfin, comme pour la syntaxe, une chaîne est attendue dans une boucle for. Par conséquent, je devais utiliser des backticks pour en faire une commande. Cependant, les boucles while attendent une syntaxe de commande, telle que IFS=, read xx yy zz.

Mike Fairhurst
la source
1
Merci, j'ai appris un peu plus que j'ai demandé de votre réponse. Je voterais également sur votre réponse, mais je ne suis pas encore autorisé et j'ai signalé la réponse acceptée sur la première.
Mike Lippert
1
Merci, c'est ce que j'espérais. Et voter est moins significatif qu'entendre votre appréciation!
Mike Fairhurst
Pour clarifier votre commentaire sous la première ligne de code: Oui, avec la Avariable non définie , bash se développe $Aen une chaîne vide, mais pour éviter toute confusion, je ne l’utiliserais pas ""car le code n’est pas équivalent à A="b" echo "". Il n'y aura aucun argument à echo.
Pabouk
8

man bash

ENVIRONNEMENT

[...] L'environnement de toute commande ou fonction simple peut être augmenté temporairement en le préfixant avec des assignations de paramètres, comme décrit ci-dessus dans PARAMÈTRES. Ces instructions d'affectation n'affectent que l'environnement vu par cette commande.

Les variables sont développées avant que l'affectation de variable ait lieu. Pour la raison évidente, cela var=xfonctionnerait dans l'autre sens également, mais var=$othervarne le ferait pas. C'est-à-dire que votre $xest nécessaire avant qu'il ne soit disponible. Mais ce n'est pas le problème principal. Le principal problème est que la ligne de commande ne peut être modifiée que par l'environnement shell mais que l'affectation ne fait pas partie de l'environnement shell.

Vous mélangez les fonctionnalités: vous souhaitez remplacer la ligne de commande mais placez la définition de la variable dans l'environnement des commandes. Les remplacements de ligne de commande doivent être effectués par le shell. L'environnement doit être explicitement utilisé par la commande appelée. Si et comment cela est fait dépend de la commande.

L'avantage de cette utilisation est que vous pouvez définir l'environnement pour un sous-processus sans affecter l'environnement shell.

x="once upon" y="a time" bash -c 'echo $x $y'

fonctionne comme prévu car, dans ce cas, les deux fonctions sont combinées: Le remplacement de la ligne de commande n’est pas effectué par le shell appelant, mais par le shell du sous-processus.

Hauke ​​Laging
la source
1
C'est un peu plus subtil que cela, car l'exemple fonctionne également x="once upon" y="a time" eval 'echo $x $y'lorsqu'il n'y a pas de sous-processus impliqué, car il evals'agit d'un processus intégré. Je suppose que la citation pertinente de la page de manuel est The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments. Considérant l’exemple de la question, il doit en être ainsi puisque readc’est aussi un processus intégré qui fonctionne avec un état de changement temporel IFS.
David Ongaro
4

La commande que vous fournissez est différente car $xet $yest développée avant l' echoexécution de la commande. Par conséquent, leurs valeurs dans le shell actuel sont utilisées et non les valeurs qui echoseraient affichées dans son environnement si elles devaient être examinées.

chepner
la source
Comment peut-on développer quelque chose dans la ligne de commande après l'exécution de la commande ...?
Hauke ​​Laging
Les affectations préalables à la commande xet ydestinées à l'environnement qui echos'exécute, pas à l'environnement dans lequel les arguments echosont développés. Car IFS=, read xx yy zztoute la chaîne est lue, non coupée, par la readcommande. Ensuite , cette chaîne de caractères est divisée en fonction de la valeur de IFS, avec les pièces correspondantes attribuées à xx, yyet zz.
Chepner
Je voulais simplement souligner que la formulation "développé avant que la commande ne soit exécutée" n'a pas beaucoup de sens car après le début de la commande, rien n'est plus développé. De plus: n'avez-vous pas regardé ma réponse ou croyez-vous néanmoins que j'ai besoin d'une explication de ce qui se passe ...?
Hauke ​​Laging
1
Je n’ai jamais prétendu avoir besoin d’une explication, ni que tout puisse être développé après l’exécution de la commande. Cependant, il est vrai que bashcommence par analyser la ligne de commande donnée, reconnaît qu'il y a deux affectations de variable à appliquer à l'environnement de la commande à venir, identifie la commande à exécuter ( echo), développe tous les paramètres trouvés dans les arguments, puis exécute la commande echoavec les arguments développés.
Chepner
Dans le cas de, echoje n'étais pas sûr s'il pouvait "voir" la variable, car il s'agit d'une commande intégrée et ne s'exécute donc pas dans un sous-shell pouvant avoir son propre environnement. Mais je l'ai essayé avec evalqui est aussi un construit et il le sait en effet. Par exemple, essayez les a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $aémissions qui ane sont définies que dans, evalmême si le pid est identique et que l'environnement n'est pas modifié pendant eval! (Pour pouvoir y accéder, /procvous devez exécuter ceci sous Linux.) Il semble que bash fasse de la magie supplémentaire ici.
David Ongaro
2

Je vais pour la plus grande image de " pourquoi c'est légal"

Réponse: Pour que vous puissiez appeler ou appeler un programme et pour cette invocation, utilisez uniquement une variable avec une variable particulière.

Par exemple: vous avez un paramètre pour une connexion à une base de données appelé "db_connection", et vous transmettez normalement "test" comme nom de votre connexion à la base de données test. En fait, vous pouvez même le définir comme paramètre par défaut que vous n'avez pas besoin de transmettre explicitement. Parfois, cependant, vous voulez travailler avec la base de données ci. Ainsi, vous transmettez le paramètre en tant que "ci", puis le programme appelé utilise ce paramètre de base de données comme nom de la base de données à utiliser pour tous les appels de base de données. Pour la prochaine exécution, si vous ne répétez pas l'approche et appelez simplement le programme, la variable reviendra à sa valeur par défaut précédente.

Michael Durrant
la source
0

Vous pouvez également utiliser ;. Il sera évalué auparavant car c'est un séparateur de commandes.

x="once upon" y="a time"; echo $x $y
camabeh
la source
1
Cela crée deux variables shell et ne crée pas de variables d'environnement dans l'utilitaire donné. Ceci est une chose très différente. Il y a aussi l'effet secondaire que le shell actuel contient maintenant de nouvelles variables.
Kusalananda