Jakie zakresy mogą mieć zmienne powłoki?

42

Właśnie natknąłem się na problem, który pokazuje mi, że nie mam jasności co do zakresu zmiennych powłoki.

Próbowałem użyć bundle install, czyli komendy Ruby, która używa wartości $GEM_HOMEdo wykonania swojej pracy. Ustawiłem $GEM_HOME, ale polecenie zignorowało tę wartość, dopóki jej nie użyłem export, jak w export GEM_HOME=/some/path.

Czytam, że dzięki temu zmienna jest w jakiś sposób „globalna” (znana również jako zmienna środowiskowa ), ale nie rozumiem, co to oznacza. Wiem o globalizacji w programowaniu, ale nie w różnych programach.

Ponadto, biorąc pod uwagę, że moje ustawienie takich zmiennych dotyczy tylko bieżącej sesji powłoki, jak ustawiłbym je dla, powiedzmy, demonizowanego procesu?

Jakie zakresy mogą mieć zmienne powłoki?

Nathan Long
źródło

Odpowiedzi:

33

Procesy są zorganizowane jako drzewo: każdy proces ma unikalny element nadrzędny, poza inittym PIDzawsze ma wartość 1 i nie ma elementu nadrzędnego.

Stworzenie nowego procesu idzie zwykle przez parę fork/ execvwywołań systemowych, gdzie środowisko procesu dziecko jest kopią procesu macierzystego.

Aby umieścić zmienną w środowisku z powłoki, musisz do exporttej zmiennej, aby była widoczna rekurencyjnie dla wszystkich dzieci. Należy jednak pamiętać, że jeśli dziecko zmieni wartość zmiennej, zmieniona wartość jest widoczna tylko dla niej i dla wszystkich procesów utworzonych po tej zmianie (jest to kopia , jak wcześniej wspomniano).

Weź również pod uwagę, że proces potomny może zmienić swoje środowisko, na przykład może zresetować go do wartości domyślnych, jak to prawdopodobnie zrobiono loginna przykład.

enzotib
źródło
1
Ach! OK, zobaczmy, czy to rozumiem. W powłoce, jeśli tak powiem FOO=bar, ustawia wartość dla bieżącego procesu powłoki. Jeśli następnie uruchomię program podobny do ( bundle install), tworzy to proces potomny, do którego nie ma dostępu FOO. Ale gdybym powiedział export FOO=bar, proces potomny (i jego potomkowie) mieliby do niego dostęp. Jeden z nich mógłby z kolei wezwać export FOO=buzzdo zmiany wartości swoich potomków lub po prostu FOO=buzzdo zmiany wartości tylko dla siebie. Czy to w porządku?
Nathan Long
2
@NathanLong To nie jest dokładnie to: we wszystkich nowoczesnych powłokach zmienna jest albo eksportowana (a więc każda zmiana wartości jest odzwierciedlana w środowisku potomków), albo nie jest eksportowana (co oznacza, że ​​zmienna nie znajduje się w środowisku). W szczególności, jeśli zmienna jest już w środowisku podczas uruchamiania powłoki, jest eksportowana.
Gilles „SO- przestań być zły”
2
Byłem trochę zdezorientowany zdaniem „jeśli dziecko zmieni wartość zmiennej, zmieniona wartość jest widoczna tylko dla niej i dla wszystkich procesów utworzonych po tej zmianie”. Bardziej słuszne byłoby powiedzenie „... widoczne dla niego i wszystkich jego procesów potomnych utworzonych po tej zmianie” - nie ma to wpływu na inne procesy potomne procesu nadrzędnego, nawet te rozpoczęte po procesie potomnym.
Jaan
26

Przynajmniej poniżej kshi bashzmienne mogą mieć trzy zakresy, a nie dwa, jak wszystkie pozostałe odpowiedzi obecnie mówią.

Oprócz eksportowanych zmiennych (tj. Środowiskowych) i nieeksportowanych zakresów zmiennych powłoki istnieje również trzeci węższy dla zmiennych lokalnych funkcji.

Zmienne zadeklarowane w funkcjach powłoki za pomocą typesettokena są widoczne tylko wewnątrz funkcji, w których są zadeklarowane, oraz w wywoływanych stamtąd funkcjach (pod).

Ten ksh/ bashkod:

# Create a shell script named /tmp/show that displays the scoped variables values.    
echo 'echo [$environment] [$shell] [$local]' > /tmp/show
chmod +x /tmp/show

# Function local variable declaration
function f
{
    typeset local=three
    echo "in function":
    . /tmp/show 
}

# Global variable declaration
export environment=one

# Unexported (i.e. local) variable declaration
shell=two

# Call the function that creates a function local variable and
# display all three variable values from inside the function
f

# Display the three values from outside the function
echo "in shell":
. /tmp/show 

# Display the same values from a subshell
echo "in subshell":
/tmp/show

# Display the same values from a disconnected shell (simulated here by a clean environment start)
echo "in other shell"
env -i /tmp/show 

tworzy ten wynik:

in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []

Jak widać, wyeksportowana zmienna jest wyświetlana z pierwszych trzech lokalizacji, niewymportowane zmienne nie są wyświetlane poza bieżącą powłoką, a zmienna lokalna funkcji nie ma wartości poza samą funkcją. Ostatni test w ogóle nie pokazuje żadnych wartości, ponieważ wyeksportowane zmienne nie są współużytkowane między powłokami, tzn. Można je tylko odziedziczyć, a na dziedziczoną wartość nie może mieć wpływu powłoka nadrzędna.

Zauważ, że to ostatnie zachowanie jest zupełnie inne niż w systemie Windows, w którym można używać Zmiennych Systemowych, które są w pełni globalne i współużytkowane przez wszystkie procesy.

jlliagre
źródło
12

Ich zakres zależy od procesu

Inni respondenci pomogli mi zrozumieć, że zakres zmiennych powłoki dotyczy procesów i ich potomków .

Kiedy wpiszesz polecenie jak lsw wierszu poleceń, faktycznie uruchamiasz proces uruchamiania lsprogramu. Nowy proces ma powłokę jako swojego rodzica.

Każdy proces może mieć własne zmienne „lokalne”, które nie są przekazywane do procesów potomnych. Może również ustawiać zmienne „środowiskowe”, które są. Używanie exporttworzy zmienną środowiskową. W obu przypadkach niepowiązane procesy (równorzędne oryginału) nie zobaczą zmiennej; kontrolujemy tylko to, co widzą procesy potomne.

Załóżmy, że masz powłokę bash, którą nazwiemy A. Wpisujesz bash, która tworzy potomną powłokę bash procesu, którą nazwiemy B. Wszystko, co wywołałeś exportw A, nadal będzie ustawione w B.

Teraz w B mówisz FOO=b. Stanie się jedna z dwóch rzeczy:

  • Jeśli B nie otrzyma (od A) zmiennej środowiskowej o nazwie FOO, utworzy zmienną lokalną. Dzieci B nie otrzymają go (chyba że dzwoni B export).
  • Jeśli B nie otrzyma (od a) zatytułowany jako zmienna środowiskowa FOO, będzie zmodyfikować go do siebie i swoich następnie rozdwojonych dzieci . Dzieci B zobaczą wartość przypisaną przez B. Nie wpłynie to jednak wcale na A.

Oto szybkie demo.

FOO=a      # set "local" environment variable
echo $FOO  # 'a'
bash       # forks a child process for the new shell
echo $FOO  # not set
exit       # return to original shell
echo $FOO  # still 'a'

export FOO # make FOO an environment variable
bash       # fork a new "child" shell
echo $FOO  # outputs 'a'
FOO=b      # modifies environment (not local) variable
bash       # fork "grandchild" shell
echo $FOO  # outputs 'b'
exit       # back to child shell
exit       # back to original shell
echo $FOO  # outputs 'a'

Wszystko to wyjaśnia mój pierwotny problem: ustawiłem GEM_HOMEw swojej powłoce, ale kiedy zadzwoniłem bundle install, stworzyłem proces potomny. Ponieważ nie użyłem export, proces potomny nie otrzymał powłoki GEM_HOME.

Cofnięcie eksportu

Możesz „cofnąć eksport” zmiennej - zapobiegając jej przekazywaniu do dzieci - za pomocą export -n FOO.

export FOO=a   # Set environment variable
bash           # fork a shell
echo $FOO      # outputs 'a'
export -n FOO  # remove environment var for children
bash           # fork a shell
echo $FOO      # Not set
exit           # back up a level
echo $FOO      # outputs 'a' - still a local variable
Nathan Long
źródło
1
Kiedy powiesz „zmodyfikuje to dla siebie i swoich dzieci”, powinieneś wyjaśnić, że tylko dzieci utworzone po modyfikacji zobaczą zmodyfikowaną wartość.
enzotib
1
@enzotib - dobry punkt. Zaktualizowano
Nathan Long
3

Najlepsze wyjaśnienie dotyczące eksportowania, jakie mogę znaleźć, to:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html

Zmienna ustawiona w podpowłoce lub powłoce podrzędnej jest widoczna tylko dla podpowłoki, w której jest zdefiniowana. Wyeksportowana zmienna faktycznie jest zmienną środowiskową. Żeby było jasne, bundle installwykonuje własną powłokę, która nie widzi, $GEM_HOMEchyba że zostanie environmentzmieniona jako eksportowana aka.

Możesz zajrzeć do dokumentacji dla zakresu zmiennego tutaj:

http://www.tldp.org/LDP/abs/html/subshells.html

Karlson
źródło
Ach, więc niepoprawnie użyłem terminu „zmienna środowiskowa” dla FOO=bar; musisz użyć, exportaby to zrobić. Pytanie odpowiednio poprawione.
Nathan Long
Spójrz na link, który dodałem.
Karlson
3

Zgodnie z oczekiwaniami istnieje hierarchia zakresów zmiennych.

Środowisko

Najbardziej zewnętrznym zakresem jest środowisko. Jest to jedyny zakres zarządzany przez system operacyjny i dlatego istnieje gwarancja, że ​​będzie istniał dla każdego procesu. Po uruchomieniu procesu otrzymuje kopię środowiska rodzica, po czym oba stają się niezależne: modyfikowanie środowiska dziecka nie zmienia środowiska rodzica, a modyfikowanie środowiska rodzica nie zmienia środowiska już istniejącego.

Zmienne powłoki

Powłoki mają własne pojęcie zmiennych. To tutaj zaczyna się nieco mylić.

Gdy przypisujesz wartość do zmiennej w powłoce, a ta zmienna już istnieje w środowisku, zmienna środowiskowa otrzymuje nową wartość. Jednak jeśli zmienna nie znajduje się jeszcze w środowisku, staje się zmienną powłoki . Zmienne powłoki istnieją tylko w procesie powłoki, podobnie jak zmienne Ruby istnieją tylko w skrypcie Ruby. Nigdy nie są dziedziczone przez procesy potomne.

Oto, gdzie exportsłowo kluczowe wchodzi w grę. Kopiuje zmienną powłoki do środowiska procesu powłoki, umożliwiając dziedziczenie procesów potomnych.

Zmienne lokalne

Zmienne lokalne to zmienne powłoki, których zakres obejmuje bloki kodu je zawierające. Deklarujesz zmienne lokalne za pomocą typesetsłowa kluczowego (przenośny) lub locallub declare(Bash). Podobnie jak inne zmienne powłoki, zmienne lokalne nie są dziedziczone przez procesy potomne. Nie można również eksportować zmiennych lokalnych.

Wąż
źródło