Jag stötte på ett problem som visar att jag inte är klar över skalvariablernas omfattning.
Jag försökte använd bundle install
, vilket är ett Ruby-kommando som använder värdet av $GEM_HOME
för att utföra sitt arbete. Jag hade ställt in $GEM_HOME
, men kommandot ignorerade det värdet tills jag använde export
, som i export GEM_HOME=/some/path
.
Jag läste att detta på något sätt gör variabeln ”global” (även känd som en miljövariabel ), men jag vet inte förstå vad det betyder. Jag känner till globaler i programmering, men inte över olika program.
Med tanke på att min inställning av sådana variabler endast gäller den aktuella shell-sessionen, hur skulle jag ställa in dem för, till exempel, en demoniserad process?
Vilka omfattningar kan skalvariabler ha?
Svar
Processerna är organiserade som ett träd: varje process har en unik förälder, förutom init
som PID
är alltid 1 och har ingen förälder.
Skapandet av en ny process går generellt genom ett par fork
/ execv
systemanrop, där miljön i barnprocessen är en kopia av den överordnade processen.
För att placera en variabel i miljön från skalet måste du export
variabeln så att den är synlig rekursivt för alla barn. Men var medveten om att om ett barn ändrar värdet på en variabel, är det ändrade värdet bara synligt för det och alla processer som skapas efter den ändringen (som en kopia , som tidigare sade).
Ta också hänsyn till att en underordnad process kan ändra sin miljö, till exempel kan återställa den till standardvärden, vilket troligen görs från login
för exempel.
Kommentarer
Svar
Vid minst under ksh
och bash
, kan variabler ha tre omfattningar, inte två som alla återstående svar för närvarande säger.
I Förutom de exporterade (dvs miljö) variabla och skal oexporterade variabla omfattningar finns det också en tredje smalare för lokala lokala variabler.
Variabler deklarerade i skalfunktioner med typeset
token syns bara inuti de funktioner de deklareras i och i (under) funktioner som anropas därifrån.
Denna ksh
/ bash
kod:
# 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
producerar denna utgång:
in function: [one] [two] [three] in shell: [one] [two] [] in subshell: [one] [] [] in other shell [] [] []
Som du kan se visas den exporterade variabeln från de tre första platserna, de icke exporterade variablerna visas inte utanför det aktuella skalet och funktionen lokal variabel har inget värde utanför själva funktionen. Det senaste testet visar inga värden alls, det beror på att exporterade variabler inte delas mellan skal, dvs. de kan bara ärvas och det ärvda värdet kan inte påverkas efteråt av det överordnade skalet.
Observera att det senare beteendet är helt annorlunda än Windows där du kan använda systemvariabler som är helt globala och delas av alla processer.
Svar
De omfattas av processen
De andra svararna hjälpte mig att förstå att skalvariabelt omfång handlar om processer och deras ättlingar .
När du skriver ett kommando som ls
på kommandoraden är du faktiskt gafflar en process för att köra ls
-programmet. Den nya processen har ditt skal som dess överordnade.
Varje process kan ha sina egna ”lokala” variabler, vilka är skickas inte till underordnade processer. Det kan också ställa in ”miljö” -variabler, vilka är. Med export
skapas en miljövariabel. I fallet kommer orelaterade processer (original från samma sida) inte att se variabeln, vi kontrollerar bara vad barnet förvärvar esses se.
Antag att du har ett bash-skal, som vi kommer att kalla A. Du skriver bash
, vilket skapar ett underordnat bash-skal, som vi ”kommer att kalla B. Allt du kallade export
på i A kommer fortfarande att ställas in i B.
Nu, i B säger du FOO=b
. En av två saker kommer att hända:
- Om B inte fick (från A) en miljövariabel som heter
FOO
skapas en lokal variabel. Barn till B får inte det (om inte B ringerexport
). - Om B gjorde ta emot (från A) en miljövariabel kallad
FOO
, den modifierar den själv och dess efterföljande barn . Barn till B ser det värde som B tilldelade. detta påverkar dock inte A alls.
Här ”är en snabb 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"
Allt detta förklarar mitt ursprungliga problem: Jag ställde GEM_HOME
i mitt skal, men när jag ringde bundle install
, som skapade en barnprocess. Eftersom jag inte hade använt export
, fick inte barnprocessen skalet ”s GEM_HOME
.
Avporterar
Du kan ”avportera” en variabel – förhindra att den överförs till barn – genom att använda 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
Kommentarer
- När du säger ” det kommer att ändra det för sig själv och sina barn ” du bör klargöra att endast barn skapade efter ändringen kommer att se det modifierade värdet.
- @enzotib – bra poäng. Uppdaterad.
Svar
Den bästa förklaringen jag kan hitta om export är den här:
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html
Variabeln som är inställd i ett underskal eller underskal är endast synlig för den subshell där den definieras. Den exporterade variabeln är faktiskt gjord för att vara en miljövariabel. För att vara tydlig kör ditt bundle install
sitt eget skal som inte ser $GEM_HOME
såvida det inte görs till en environment
variabel aka exporterad.
Du kan titta på dokumentationen för variabelt omfång här:
http://www.tldp.org/LDP/abs/html/subshells.html
Kommentarer
- Ah, så jag var felaktig att använda termen ” miljövariabel ” för
FOO=bar
; du måste användaexport
för att göra det till. Frågan korrigeras i enlighet med detta. - Ta en titt på länken jag har lagt till i.
Svar
Det finns som förväntat en hierarki av variabla omfattningar.
Miljö
Det yttersta omfånget är miljön. Detta är det enda omfånget hanteras av operativsystemet och garanteras därför att det finns för varje process. När en process startas får den en kopia av föräldrarnas miljö, varefter de två blir oberoende: att ändra barnets miljö förändrar inte föräldrars miljö och att ändra föräldrars miljö förändrar inte det för ett redan existerande barn.
Skalvariabler
Skal har sin egen uppfattning om variabler. Det är här saker och ting börjar bli lite förvirrande.
När du tilldelar ett värde till en variabel i ett skal, och den variabeln redan finns i miljön, får miljövariabeln det nya värdet. Men om variabeln inte finns i miljön ändå blir den en skal -variabel. Skalvariabler finns bara inom skalprocessen, liknande hur Ruby-variabler bara finns i ett Ruby-skript. De ärvs aldrig av barnprocesser.
Här är där export
nyckelordet spelar in. Det kopierar en skalvariabel till skalprocessens miljö vilket gör det möjligt för barnprocesser att ärva.
Lokala variabler
Lokala variabler är skalvariabler som omfattas av kodblocken som innehåller dem. Du förklarar lokala variabler med typeset
nyckelordet (bärbart) eller local
eller declare
(Bash ). Liksom andra skalvariabler ärvs lokala variabler inte av underordnade processer. Lokala variabler kan inte exporteras.
FOO=bar
, ställer det in värdet för den aktuella skalprocessen. Om jag sedan kör ett program som (bundle install
), som skapar en underprocess som inte ’ inte får tillgång tillFOO
. Men om jag hade sagtexport FOO=bar
skulle barnprocessen (och dess ättlingar) ha tillgång till den. En av dem kan i sin tur ringaexport FOO=buzz
för att ändra värdet för sina ättlingar, eller baraFOO=buzz
för att bara ändra värdet för sig själv . Är det ungefär rätt?