Mi sono imbattuto in un problema che mi mostra che non sono chiaro sullambito delle variabili di shell.
Stavo cercando di utilizza bundle install
, che è un comando Ruby che utilizza il valore di $GEM_HOME
per eseguire il suo lavoro. Avevo impostato $GEM_HOME
, ma il comando ha ignorato quel valore finché non ho utilizzato export
, come in export GEM_HOME=/some/path
.
Ho letto che questo rende la variabile in qualche modo “globale” (nota anche come variabile di ambiente ), ma non lo faccio capire cosa significa. Conosco le variabili globali nella programmazione, ma non attraverso programmi distinti.
Inoltre, dato che la mia impostazione di tali variabili si applica solo alla sessione corrente della shell, come le imposterei, diciamo, per un processo demonizzato?
Quali ambiti possono avere le variabili di shell?
Risposta
I processi sono organizzati come un albero: ogni processo ha un genitore univoco, a parte init
che PID
è sempre 1 e non ha un genitore.
La creazione di un nuovo processo avviene generalmente attraverso una coppia di fork
/ execv
, dove lambiente del processo secondario è una copia del processo principale.
Per mettere una variabile nellambiente dalla shell devi export
quella variabile, in modo che sia visibile ricorsivamente a tutti i figli. Ma tieni presente che se un bambino cambia il valore di una variabile, il valore modificato è visibile solo ad essa e tutti i processi creati dopo quella modifica (essendo una copia , come in precedenza detto).
Considera anche che un processo figlio potrebbe cambiare il suo ambiente, ad esempio potrebbe ripristinarlo ai valori predefiniti, come probabilmente si fa da login
per esempio.
Commenti
Risposta
A almeno sotto ksh
e bash
, le variabili possono avere tre ambiti, non due come tutte le risposte rimanenti stanno attualmente dicendo.
In Oltre agli ambiti delle variabili esportate (cioè di ambiente) e delle variabili non esportate della shell, ce nè anche un terzo più ristretto per le variabili locali delle funzioni.
Variabili dichiarate nelle funzioni della shell con typeset
sono visibili solo allinterno delle funzioni in cui sono dichiarati e nelle (sotto) funzioni chiamate da lì.
Questo ksh
/ bash
codice:
# 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
produce questo output:
in function: [one] [two] [three] in shell: [one] [two] [] in subshell: [one] [] [] in other shell [] [] []
Come puoi vedere, la variabile esportata viene visualizzata dalle prime tre posizioni, le variabili non esportate non vengono visualizzate al di fuori della shell corrente e la variabile locale della funzione non ha valore al di fuori della funzione stessa. Lultimo test non mostra alcun valore, questo perché le variabili esportate non sono condivise tra le shell, cioè possono solo essere ereditate e il valore ereditato non può essere influenzato successivamente dalla shell genitrice.
Nota che questultimo comportamento è abbastanza diverso da quello di Windows in cui puoi utilizzare variabili di sistema che sono completamente globali e condivise da tutti i processi.
Risposta
Hanno lambito del processo
Gli altri rispondenti mi hanno aiutato a capire che lambito della variabile di shell riguarda i processi e i loro discendenti .
Quando digiti un comando come ls
sulla riga di comando, sei effettivamente biforcare un processo per eseguire il programma ls
. Il nuovo processo ha la shell come genitore.
Ogni processo può avere le proprie variabili “locali”, che sono non viene passato ai processi figli. Può anche impostare variabili di “ambiente”, che sono. Lutilizzo di export
crea una variabile di ambiente. In entrambi case, processi non correlati (pari delloriginale) non vedranno la variabile; stiamo solo controllando quale figlio proc esses see.
Supponi di avere una shell bash, che “chiameremo A. Digiti bash
, che crea una shell bash del processo figlio, che chiameremo B. Tutto ciò che hai chiamato export
in A sarà ancora impostato in B.
Ora, in B, dici FOO=b
. Accadrà una di queste due cose:
- Se B non ha ricevuto (da A) una variabile dambiente chiamata
FOO
, creerà una variabile locale. I figli di B non lo riceveranno (a meno che B non chiamiexport
). - Se B lo ha fatto riceve (da A) una variabile dambiente chiamata
FOO
, la la modificherà da sola e i suoi figli successivamente biforcati . I figli di B vedranno il valore che B ha assegnato. Tuttavia, ciò non influirà affatto su A.
Ecco “una rapida 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"
Tutto questo spiega il mio problema originale: ho impostato GEM_HOME
nella mia shell, ma quando ho chiamato bundle install
, che ha creato un processo figlio. Poiché non avevo “t utilizzato export
, il processo figlio non ha” ricevuto la shell “GEM_HOME
.
Annullamento dellesportazione
Puoi “non esportare” una variabile, impedendone il passaggio ai figli, utilizzando 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
Commenti
- Quando dici ” lo modificherà per se stesso e per i suoi figli ” dovresti chiarire che solo i bambini hanno creato dopo la modifica vedrà il valore modificato.
- @enzotib – buon punto. Aggiornato.
Risposta
La migliore spiegazione che posso trovare sullesportazione è questa:
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html
La variabile impostata allinterno di una subshell o shell figlia è visibile solo a la subshell in cui è definita. La variabile esportata è effettivamente realizzata per essere una variabile di ambiente. Quindi, per essere chiari, il tuo bundle install
esegue la propria shell che “non vede il $GEM_HOME
a meno che non sia un environment
variabile anche esportata.
Puoi dare unocchiata alla documentazione per lambito della variabile qui:
http://www.tldp.org/LDP/abs/html/subshells.html
Commenti
- Ah, quindi non ho sbagliato a usare il termine ” variabile di ambiente ” per
FOO=bar
; devi utilizzareexport
per renderlo uno. Domanda corretta di conseguenza. - Dai unocchiata al link che ho aggiunto.
Risposta
Esiste una gerarchia di ambiti variabili, come previsto.
Ambiente
Lambito più esterno è lambiente. Questo è lunico ambito gestito dal sistema operativo ed è quindi garantita lesistenza per ogni processo Quando un processo viene avviato riceve un file copia dellambiente del genitore dopo di che i due diventano indipendenti: la modifica dellambiente del figlio non cambia quello del genitore, e la modifica dellambiente del genitore non cambia quello di un figlio già esistente.
Variabili di shell
Le shell hanno una propria nozione di variabili. È qui che le cose iniziano a diventare un po confuse.
Quando si assegna un valore a una variabile in una shell e quella variabile esiste già nellambiente, la variabile dambiente riceve il nuovo valore. Tuttavia, se la variabile non è ancora nellambiente diventa una variabile shell . Le variabili di shell esistono solo allinterno del processo di shell, in modo simile a come le variabili Ruby esistono solo allinterno di uno script Ruby. Non vengono mai ereditate da processi figlio.
Qui “è dove entra in gioco la parola chiave export
. Copia una variabile di shell nellambiente del processo di shell, rendendo possibile lereditarietà dei processi figli.
Variabili locali
Le variabili locali sono variabili di shell che hanno come ambito i blocchi di codice che le contengono. Dichiari le variabili locali con la typeset
parola chiave (portabile) o local
o declare
(Bash ). Come altre variabili di shell, le variabili locali non vengono ereditate dai processi figli. Inoltre, le variabili locali non possono essere esportate.
FOO=bar
, viene impostato il valore per il processo di shell corrente. Se poi eseguo un programma come (bundle install
), viene creato un processo figlio, che ‘ non ottiene laccesso aFOO
. Ma se avessi dettoexport FOO=bar
, il processo figlio (e i suoi discendenti) avrebbero accesso ad esso. Uno di loro potrebbe, a sua volta, chiamareexport FOO=buzz
per modificare il valore per i suoi discendenti o semplicementeFOO=buzz
per modificare il valore solo per se stesso . È più o meno giusto?