¿Qué ámbitos pueden tener las variables de shell?

42

Me encontré con un problema que me muestra que no tengo claro el alcance de las variables de shell.

Estaba tratando de usar bundle install, que es un comando Ruby que usa el valor de $GEM_HOMEpara hacer su trabajo. Lo había configurado $GEM_HOME, pero el comando ignoró ese valor hasta que lo usé export, como en export GEM_HOME=/some/path.

Leí que esto hace que la variable sea de alguna manera "global" (también conocida como variable de entorno ), pero no entiendo lo que eso significa. Sé acerca de los globales en la programación, pero no a través de distintos programas.

Además, dado que mi configuración de tales variables se aplica solo a la sesión de shell actual, ¿cómo las establecería para, por ejemplo, un proceso demonizado?

¿Qué ámbitos pueden tener las variables de shell?

Nathan Long
fuente

Respuestas:

33

Los procesos están organizados como un árbol: cada proceso tiene un padre único, aparte del initcual PIDsiempre es 1 y no tiene padre.

La creación de un nuevo proceso generalmente pasa por un par de llamadas fork/ execvsystem, donde el entorno del proceso secundario es una copia del proceso primario.

Para poner una variable en el entorno desde el shell, debe tener exportesa variable, de modo que sea visible de forma recursiva para todos los niños. Pero tenga en cuenta que si un niño cambia el valor de una variable, el valor cambiado solo es visible para él y todos los procesos creados después de ese cambio (siendo una copia , como se dijo anteriormente).

Tenga en cuenta también que un proceso secundario podría cambiar su entorno, por ejemplo, podría restablecerlo a los valores predeterminados, como probablemente se haga, loginpor ejemplo.

enzotib
fuente
1
Ah! Bien, veamos si entiendo esto. En el shell, si digo FOO=bar, eso establece el valor para el proceso actual del shell. Si luego ejecuto un programa como ( bundle install), eso crea un proceso secundario, al que no tiene acceso FOO. Pero si hubiera dicho export FOO=bar, el proceso hijo (y sus descendientes) tendrían acceso a él. Uno de ellos podría, a su vez, llamar export FOO=buzzpara cambiar el valor de sus descendientes, o simplemente FOO=buzzpara cambiar el valor solo para sí mismo. ¿Eso es correcto?
Nathan Long
2
@NathanLong Eso no es exactamente así: en todos los shells modernos, una variable se exporta (y, por lo tanto, cualquier cambio en el valor se refleja en el entorno de los descendientes) o no se exporta (lo que significa que la variable no está en el entorno). En particular, si la variable ya está en el entorno cuando se inicia el shell, se exporta.
Gilles 'SO- deja de ser malvado'
2
Estaba un poco confundido por la oración "si un niño cambia el valor de una variable, el valor cambiado solo es visible para él y todos los procesos creados después de ese cambio". Sería más correcto decir "... visible para él y todos sus procesos descendientes creados después de ese cambio": los otros elementos secundarios del proceso principal, incluso los que comenzaron después del proceso secundario, no se ven afectados.
Jaan
26

Al menos bajo kshy bash, las variables pueden tener tres ámbitos, no dos como todas las respuestas restantes están diciendo actualmente.

Además de la variable exportada (es decir, de entorno) y los ámbitos de variables de shell no exportados, también hay una tercera más estrecha para las variables locales de función.

Las variables declaradas en funciones de shell con el typesettoken solo son visibles dentro de las funciones en las que se declaran y en (sub) funciones llamadas desde allí.

Este ksh/ bashcódigo:

# 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 esta salida:

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

Como puede ver, la variable exportada se muestra desde las tres primeras ubicaciones, las variables no exportadas no se muestran fuera del shell actual y la variable local de la función no tiene ningún valor fuera de la función misma. La última prueba no muestra ningún valor, esto se debe a que las variables exportadas no se comparten entre shells, es decir, solo pueden heredarse y el valor heredado no puede verse afectado posteriormente por el shell padre.

Tenga en cuenta que este último comportamiento es bastante diferente del de Windows, donde puede usar variables del sistema que son totalmente globales y compartidas por todos los procesos.

jlliagre
fuente
12

Son alcanzados por proceso

Los otros respondedores me ayudaron a comprender que el alcance variable de shell se trata de procesos y sus descendientes .

Cuando escribe un comando como lsen la línea de comando, en realidad está bifurcando un proceso para ejecutar el lsprograma. El nuevo proceso tiene su shell como padre.

Cualquier proceso puede tener sus propias variables "locales", que no se pasan a los procesos secundarios. También puede establecer variables de "entorno", que son. El uso exportcrea una variable de entorno. En cualquier caso, los procesos no relacionados (pares del original) no verán la variable; solo estamos controlando lo que ven los procesos secundarios.

Supongamos que tiene un shell bash, al que llamaremos A. Usted escribe bash, lo que crea un shell bash de proceso hijo, al que llamaremos B. Cualquier cosa que haya llamado exporten A todavía se establecerá en B.

Ahora, en B, dices FOO=b. Sucederá una de dos cosas:

  • Si B no recibió (de A) una variable de entorno llamada FOO, creará una variable local. Los niños de B no lo recibirán (a menos que B llame export).
  • Si B no recibir (de A) una variable de entorno callled FOO, se modificarlo para sí y sus hijos posteriormente bifurcadas . Los hijos de B verán el valor que B asignó. Sin embargo, esto no afectará a A en absoluto.

Aquí hay una demostración rápida.

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'

Todo esto explica mi problema original: configuré GEM_HOMEmi shell, pero cuando llamé bundle install, eso creó un proceso secundario. Como no lo había usado export, el proceso hijo no recibió el shell GEM_HOME.

No exportar

Puede "desexportar" una variable, evitando que se pase a los niños, mediante el uso 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
fuente
1
Cuando dice "lo modificará para sí mismo y sus elementos secundarios", debe aclarar que solo los elementos secundarios creados después de la modificación verán el valor modificado.
enzotib
1
@enzotib - buen punto. Actualizado.
Nathan Long
3

La mejor explicación que puedo encontrar sobre la exportación es esta:

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

La variable establecida dentro de un subshell o un shell hijo solo es visible para el subshell en el que se define. La variable exportada está hecha para ser una variable de entorno. Entonces, para ser claros, bundle installejecuta su propio shell, que no ve a $GEM_HOMEmenos que se convierta en una environmentvariable también conocida como exportada.

Puede echar un vistazo a la documentación para el alcance variable aquí:

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

Karlson
fuente
Ah, entonces era incorrecto usar el término "variable de entorno" para FOO=bar; tienes que usar exportpara hacerlo uno. Pregunta corregida en consecuencia.
Nathan Long
Eche un vistazo al enlace que he agregado.
Karlson
3

Hay una jerarquía de ámbitos variables, como se esperaba.

Medio ambiente

El alcance más externo es el medio ambiente. Este es el único alcance administrado por el sistema operativo y, por lo tanto, se garantiza que existe para cada proceso. Cuando se inicia un proceso, recibe una copia del entorno de su padre después de lo cual los dos se vuelven independientes: modificar el entorno del niño no cambia el del padre y modificar el entorno del padre no cambia el de un niño ya existente.

Variables de Shell

Los shells tienen su propia noción de variables. Aquí es donde las cosas comienzan a ser un poco confusas.

Cuando asigna un valor a una variable en un shell, y esa variable ya existe en el entorno, la variable de entorno recibe el nuevo valor. Sin embargo, si la variable aún no está en el entorno, se convierte en una variable de shell . Las variables de shell solo existen dentro del proceso de shell, de forma similar a como las variables de Ruby solo existen dentro de un script de Ruby. Nunca son heredados por procesos hijos.

Aquí es donde exportentra en juego la palabra clave. Copia una variable de shell en el entorno del proceso de shell, lo que hace posible que los procesos secundarios hereden.

Variables locales

Las variables locales son variables de shell en los bloques de código que las contienen. Usted declara variables locales con la typesetpalabra clave (portable) o localo declare(Bash). Al igual que otras variables de shell, los procesos secundarios no heredan las variables locales. Tampoco se pueden exportar variables locales.

Serpiente
fuente