Pourquoi ne puis-je pas imprimer une variable que je peux voir dans la sortie d'env?

9

Je souhaite définir des variables d'environnement d'une instance de shell à partir d'une autre. J'ai donc décidé de faire des recherches. Après avoir lu un certain nombre de questions à ce sujet, j'ai décidé de le tester.

J'ai engendré deux obus A et B (PID 420), tous deux en marche zsh. Depuis le shell, AI a exécuté ce qui suit.

sudo gdb -p 420
(gdb) call setenv("FOO", "bar", 1)
(gdb) detach

Depuis le shell B lorsque je cours, envje peux voir que la variable FOO est en effet définie avec une valeur de bar. Cela me fait penser que FOO a été initialisé avec succès dans l'environnement du shell B. Cependant, si j'essaie d'imprimer FOO, j'obtiens une ligne vide indiquant qu'elle n'est pas définie. Pour moi, c'est comme s'il y avait une contradiction ici.

Cela a été testé sur mon propre système Arch GNU / Linux et sur une machine virtuelle Ubuntu. J'ai également testé cela sur les bashendroits où la variable n'apparaissait même pas en env. Bien que décevant pour moi, cela a du sens si le shell met en cache une copie de son environnement au moment du frai et ne l'utilise que (ce qui a été suggéré dans l'une des questions liées). Cela ne répond toujours pas pourquoi zshpeut voir la variable.

Pourquoi la sortie est-elle echo $FOOvide?


ÉDITER

Après l'entrée dans les commentaires, j'ai décidé de faire un peu plus de tests. Les résultats sont visibles dans les tableaux ci-dessous. Dans la première colonne se trouve le shell dans lequel la FOOvariable a été injectée. La première ligne contient la commande dont la sortie peut être vue en dessous. La variable FOOa été injecté à l' aide: sudo gdb -p 420 -batch -ex 'call setenv("FOO", "bar", 1)'. Les commandes spécifiques à zsh: zsh -c '...'ont également été testées à l'aide de bash. Les résultats étaient identiques, leur sortie a été omise par souci de concision.

Arch GNU / Linux, zsh 5.3.1, bash 4.4.12 (1)

|      |  env | grep FOO  | echo $FOO |  zsh -c 'env | grep FOO'  |  zsh -c 'echo $FOO'  |         After export FOO          |
|------|------------------|-----------|---------------------------|----------------------|-----------------------------------|
| zsh  |  FOO=bar         |           | FOO=bar                   | bar                  | No Change                         |
| bash |                  | bar       |                           |                      | Value of FOO visible in all tests |

Ubuntu 16.04.2 LTS, zsh 5.1.1, bash 4.3.48 (1)

|      |  env | grep FOO  | echo $FOO |  zsh -c 'env | grep FOO'  |  zsh -c 'echo $FOO'  |         After export FOO          |
|------|------------------|-----------|---------------------------|----------------------|-----------------------------------|
| zsh  |  FOO=bar         |           | FOO=bar                   | bar                  | No Change                         |
| bash |                  | bar       |                           |                      | Value of FOO visible in all tests |

Ce qui précède semble impliquer que les résultats sont indépendants de la distribution. Cela ne m'en dit pas beaucoup plus zshet bashgère différemment le réglage des variables. De plus, export FOOa un comportement très différent dans ce contexte selon le shell. Espérons que ces tests puissent clarifier quelque chose pour quelqu'un d'autre.

rlf
la source
Que se passe-t-il si vous faites un zsh -c 'echo $FOO'(utilisez des guillemets simples!) À la place? Le voyez-vous alors?
user1934428
La valeur correcte est imprimée à partir d'un nouveau sous-shell (testé pour bash child également). De toute évidence, l'environnement est persistant d'une manière ou d'une autre, car l'enfant peut en hériter, mais pourquoi le parent ne l'honore-t-il pas?
rlf
3
C'est ce que je pensais. Je suppose que le shell a quelque part une table de symboles de variables, certaines d'entre elles sont marquées comme "exportées", ce qui signifie qu'à l'ouverture d'un sous-shell, elles sont placées dans l'environnement du processus enfant. Initialement (lorsque le shell démarre), les variables de l'environnement à ce moment-là sont copiées dans la table des symboles (bien sûr aussi en tant que variables "exportées"). Lorsque vous modifiez l'environnement, le shell n'est pas remarqué pour mettre à jour leur table de symboles - mais les processus enfants (comme env) voient l'environnement modifié.
user1934428
2
J'ai testé sur Ubuntu 16.04 avec zsh 5.1.1 et bash 4.3.48 (1) et il semble que la définition d'une variable d'environnement pour zshdans GDB ne la rend pas visible en tant que variable shell mais la fait passer à des processus enfants (comme vous l'avez observé), alors que définir un pour le bash rend visible en tant que variable shell mais ne le fait pas être transmis aux processus enfants! Il semble que zsh et bash utilisent différentes stratégies pour gérer les variables, avec zsh qui suit les variables non environnementales et bash stocke tout dans son environnement qu'il nettoie lors du lancement d'un enfant (non sous-shell).
Eliah Kagan
@EliahKagan, intéressant; vous devez poster cela comme réponse. Je me demande aussi si cela fait une différence si vous exécutez export FOOdans bash?
Wildcard

Réponses:

2

La plupart des shells n'utilisent pas l' API getenv()/ setenv()/ putenv().

Au démarrage, ils créent des variables shell pour chaque variable d'environnement. Ceux-ci seront stockés dans des structures internes qui ont besoin de transporter d'autres informations comme si la variable est exportée, en lecture seule ... Ils ne peuvent pas utiliser les libc environpour cela.

De même, et pour cette raison, ils ne seront pas utiliser execlp(), execvp()pour exécuter des commandes mais appeler l' execve()appel système directement, le calcul du envp[]tableau en fonction de la liste de leurs variables exportées.

Donc, dans votre gdb, vous devez ajouter une entrée à cette table interne de variables de shell, ou éventuellement appeler la bonne fonction qui lui ferait interpréter un export VAR=valuecode pour mettre à jour cette table par elle-même.

Quant à savoir pourquoi vous voyez une différence entre bashet zshlorsque vous appelez setenv()dans gdb, je pense que ce que vous appelez setenv()avant que le shell initialise, par exemple lors de l' entrée main().

Vous remarquerez que bashl « main()est int main(int argc, char* argv[], char* envp[])(et les bashcartes des variables de celles env VARs envp[]) , alors que zshl » est int main(int argc, char* argv[])et zshobtient les variables de la environplace. setenv()modifie environmais ne peut pas modifier envp[]sur place (lecture seule sur plusieurs systèmes ainsi que les chaînes vers lesquelles pointent ces pointeurs).

Dans tous les cas, après la lecture du shell environau démarrage, l'utilisation setenv()serait inefficace car le shell n'utilise plus environ(ou getenv()) par la suite.

Stéphane Chazelas
la source