Dlaczego nie mogę wydrukować zmiennej, którą widzę na wyjściu env?

9

Interesuje mnie ustawienie zmiennych środowiskowych jednej instancji powłoki od drugiej. Postanowiłem więc przeprowadzić badania. Po odczytaniu numeru z pytaniami o to postanowiłem przetestować go.

Odrodziłem dwie pociski A i B (PID 420), obie działające zsh. Z powłoki AI uruchomiono następujące.

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

Po uruchomieniu powłoki B envwidzę, że zmienna FOO jest rzeczywiście ustawiona na wartość bar. To sprawia, że ​​myślę, że FOO został pomyślnie zainicjowany w środowisku powłoki B. Jednak jeśli spróbuję wydrukować FOO, otrzymam pustą linię, co oznacza, że ​​nie jest ustawiony. Dla mnie wydaje się, że istnieje tu sprzeczność.

Zostało to przetestowane zarówno na moim systemie Arch GNU / Linux, jak i na maszynie Wirtualnej Ubuntu. Przetestowałem to również bashtam, gdzie zmienna nawet nie pojawiła się w env. Jest to dla mnie rozczarowujące, ma sens, jeśli powłoka buforuje kopię swojego środowiska w czasie odrodzenia i używa tylko tego (co zasugerowano w jednym z powiązanych pytań). To wciąż nie odpowiada, dlaczego zshmożna zobaczyć zmienną.

Dlaczego wyjście jest echo $FOOpuste?


EDYTOWAĆ

Po wprowadzeniu uwag w komentarzach postanowiłem przeprowadzić nieco więcej testów. Wyniki można zobaczyć w poniższych tabelach. W pierwszej kolumnie znajduje się powłoka, do której FOOwstrzyknięto zmienną. Pierwszy wiersz zawiera polecenie, którego wynik można zobaczyć pod nim. Zmienna FOObył wstrzykiwany przy użyciu: sudo gdb -p 420 -batch -ex 'call setenv("FOO", "bar", 1)'. Polecenia specyficzne dla zsh: zsh -c '...'zostały również przetestowane przy użyciu bash. Wyniki były identyczne, ich wyniki pominięto ze względu na zwięzłość.

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 |

Powyższe wydaje się sugerować, że wyniki są niezależne od dystrybucji. Nie mówi mi to nic więcej zshi bashinaczej traktuję ustawianie zmiennych. Co więcej, export FOOma bardzo różne zachowanie w tym kontekście, w zależności od powłoki. Mam nadzieję, że testy te wyjaśnią komuś coś innego.

rlf
źródło
Co się stanie, jeśli zsh -c 'echo $FOO'zamiast tego użyjesz (użyj pojedynczych cudzysłowów!)? Widzisz to?
user1934428,
Prawidłowa wartość jest drukowana z nowej podpowłoki (przetestowanej również dla potomka bash). Oczywiście środowisko jest w jakiś sposób trwałe, ponieważ dziecko może je odziedziczyć, ale dlaczego rodzic go nie szanuje?
rlf
3
Tak myślałem. Myślę, że powłoka ma gdzieś tablicę symboli zmiennych, niektóre z nich są oznaczone jako „eksportowane”, co oznacza, że ​​po otwarciu podpowłoki są one umieszczane w środowisku procesu potomnego. Początkowo (po uruchomieniu powłoki) zmienne ze środowiska w tym czasie są kopiowane do tablicy symboli (oczywiście również jako zmienne „eksportowane”). Gdy zmienisz środowisko, powłoka nie zostanie zauważona, aby zaktualizować swoją tablicę symboli - ale procesy potomne (jak env) widzą zmodyfikowane środowisko.
user1934428,
2
Testowałem na Ubuntu 16.04 z zsh 5.1.1 i bash 4.3.48 (1) i wydaje się, że ustawienie zmiennej środowiskowej dla zshGDB nie powoduje, że jest ona widoczna jako zmienna powłoki, ale powoduje, że jest ona przekazywana do procesów potomnych (jako zaobserwowano), podczas gdy ustawienie jednego bash powoduje, że jest on widoczny jako zmienna powłoki, ale nie powoduje, że jest on przekazywany do procesów potomnych! Wygląda na to, że zsh i bash używają różnych strategii zarządzania zmiennymi, z śledzeniem zmiennych nieśrodowiskowych za pomocą zsh i przechowywaniem wszystkiego w swoim środowisku, które odkaża podczas uruchamiania potomka (niebędącego podpowłoką).
Eliah Kagan,
@EliahKagan, ciekawe; powinieneś opublikować to jako odpowiedź. Zastanawiam się również, czy to robi różnicę, jeśli uruchomić export FOOw bash?
Wildcard

Odpowiedzi:

2

Większość powłok nie używa interfejsu API getenv()/ setenv()/ putenv().

Po uruchomieniu tworzą zmienne powłoki dla każdej zmiennej środowiskowej. Będą one przechowywane w strukturach wewnętrznych, które muszą przenosić inne informacje, takie jak to, czy zmienna jest eksportowana, tylko do odczytu ... Nie mogą do tego używać bibliotek libc environ.

Podobnie, iz tego powodu, że nie będzie używać execlp(), execvp()aby wykonywać polecenia, ale nazywają execve()wywołanie systemowe bezpośrednio, obliczania envp[]macierzy w oparciu o listę swoich eksportowanych zmiennych.

W twoim przypadku gdbmusisz dodać wpis do wewnętrznej tabeli zmiennych tej powłoki lub ewentualnie wywołać odpowiednią funkcję, która sprawi, że zinterpretuje export VAR=valuekod, aby zaktualizować tę tabelę samodzielnie.

Dlaczego widzisz różnicę pomiędzy bashi zshpodczas rozmowy setenv()w gdbPodejrzewam, że to dlatego, że dzwonisz setenv()przed inicjalizuje powłoki, na przykład przy wejściu main().

Zauważysz bash, że main()is jest int main(int argc, char* argv[], char* envp[])(i bashmapuje zmienne z tych zmiennych env envp[]), podczas gdy zshis is int main(int argc, char* argv[])i zshpobiera zmienne z nich environ. setenv()modyfikuje, environale nie może modyfikować envp[]w miejscu (tylko do odczytu w kilku systemach, a także ciągów, na które wskazują te wskaźniki).

W każdym razie, po odczytaniu powłoki environprzy uruchomieniu, użycie setenv()byłoby nieskuteczne, ponieważ powłoka nie używa environ(lub getenv()) później.

Stéphane Chazelas
źródło