Las asignaciones variables afectan el shell actual en ejecución

8

Mientras escribía un código descubrí que esta línea:

$ TZ="America/Los_Angeles"       date; echo "$TZ"
Thu Dec 24 14:39:15 PST 2015

Indica correctamente el tiempo real en "Los Ángeles" y que el valor de la variable TZno se retiene. Todo como debería esperarse.

Sin embargo, con esta línea, que solía expandir algunos formatos hasta la fecha, y que esencialmente ejecuta lo mismo, conserva el valor de TZ:

TZ="America/Los_Angeles" eval  date; echo "$TZ"
Thu Dec 24 14:41:34 PST 2015
America/Los_Angeles

Después de varias pruebas más, descubrí que esto sucede solo en algunos proyectiles. Sucede en guión, ksh pero no en bash o zsh.

Q's

Las preguntas son:

  • ¿Por qué se retiene el valor de TZ en el shell actual?
  • ¿Cómo podría evitarse / controlarse (si es posible)?

Adicional.

Realicé pruebas en varios depósitos con estas dos líneas:

myTZ="America/Los_Angeles"
unset TZ; { TZ="$myTZ"      date; } >/dev/null; echo -n "  direct $TZ"
unset TZ; { TZ="$myTZ" eval date; } >/dev/null; echo    "  evaled $TZ"

Y esto resulta:

/bin/ash        :   direct   evaled America/Los_Angeles
/bin/dash       :   direct   evaled America/Los_Angeles
/bin/sh         :   direct   evaled America/Los_Angeles
/bin/bash       :   direct   evaled
/bin/ksh93      :   direct   evaled America/Los_Angeles
/bin/lksh       :   direct   evaled America/Los_Angeles
/bin/mksh       :   direct   evaled America/Los_Angeles
/bin/zsh        :   direct   evaled
/bin/zsh4       :   direct   evaled 

El valor TZ afecta al shell en ejecución en todos los shells excepto bash y zsh.


fuente

Respuestas:

6

Como has encontrado, ese es un comportamiento específico. Pero también tiene sentido.

El valor se retiene en el entorno del shell por la misma razón por la que otros comandos retienen el valor de otras variables de entorno cuando prefijas definiciones a sus líneas de comando: estás configurando las variables en su entorno.

Las incorporaciones especiales son generalmente la variedad más intrínseca en cualquier shell: evales esencialmente un nombre accesible para el analizador de shell, setrastrea y configura las opciones de shell y los parámetros de shell, return/ break/ continueflujo de control de bucle de disparo, trapmaneja señales, execabre / cierra archivos. Estas son todas las utilidades fundamentales, y generalmente se implementan con envoltorios apenas visibles sobre la carne y las papas de su caparazón.

La ejecución de la mayoría de los comandos involucra un entorno en capas (un entorno de subshell (que no necesariamente tiene que ser un proceso separado)) que no se obtiene al llamar a los builtins especiales. Entonces, cuando configura el entorno para uno de estos comandos, configura el entorno para su shell. Porque básicamente representan tu caparazón.

Pero no son los únicos comandos que retienen el entorno de esa manera: las funciones también hacen lo mismo. Y los errores se comportan de manera diferente para las incorporaciones especiales: intente cat <doesntexisty luego intente exec <doesntexisto incluso solo : <doesntexisty, aunque el catcomando se quejará, el execo :matará a un shell POSIX. Lo mismo ocurre con los errores de expansión en su línea de comando. Son el bucle principal , básicamente.

Estos comandos no tienen que retener el entorno: algunos shells envuelven sus elementos internos con más fuerza que otros, exponen menos de la funcionalidad central y agregan más búfer entre el programador y la interfaz. Estos mismos depósitos también pueden ser un poco más lentos que otros. Definitivamente requieren muchos ajustes no estándar para que cumplan con las especificaciones. Y de todos modos, no es como si esto fuera algo malo :

fn(){ bad_command || return=$some_value return; }

Eso es fácil . ¿De qué otra forma preservaría el retorno de bad_commandmanera tan simple sin tener que establecer un montón de entorno adicional y, sin embargo, hacer asignaciones condicionalmente?

arg=$1 shift; x=$y unset y

Eso también funciona. Los intercambios en el lugar son más simples.

IFS=+  set -- "$IFS" x y z
x="$*" IFS=$1 shift
echo "${x#"$IFS"}" "$*"

+x+y+z x y z

...o...

expand(){
    PS4="$*" set -x "" "$PS4" 
    { $1; }  2>&1
    PS4=$2   set +x
}   2>/dev/null

x='echo kill my computer; $y'
y='haha! just kidding!' expand "${x##*[\`\(]*}"

... es otro que me gusta usar ...

echo kill my computer; haha! just kidding!
mikeserv
fuente
@BinaryZebra, pero el punto es que no funcionan de manera diferente, cuando establece variables para algún otro comando, persisten en el entorno de ese otro ejecutable. cuando establece variables en el entorno de su shell, también persisten.
mikeserv
3

Resulta que hay una razón muy específica para este comportamiento.
La descripción de lo que sucede es un poco más larga.

Solo tareas.

Una línea de comando hecha (solo) de asignaciones establecerá las variables para este shell.

$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>

Se retendrá el valor de los vars asignados.

Comando externo

Las asignaciones antes de un comando externo establecen variables solo para ese shell:

$ unset a b c d
$ a=b c=d bash -c 'echo "one:|$c|"'; echo "two:<$c>"
one:|d|
two:<>

Y me refiero a "externo" como cualquier comando que debe buscarse en PATH.

Esto también se aplica a las incorporaciones normales (como cd, por ejemplo):

$ unset a b c d; a=b c=d cd . ; echo "<$a::$c>"
<::>

Hasta aquí todo es como se suele esperar.

Empotrados especiales.

Pero para incorporaciones especiales, POSIX requiere que los valores estén establecidos para este shell .

  1. Las asignaciones variables especificadas con utilidades incorporadas especiales permanecen vigentes una vez que se completa la incorporación.
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

Estoy usando una llamada para shsuponer que shes un shell compatible con POSIX.

Esto no es algo que generalmente se usa.

Esto significa que las asignaciones colocadas delante de cualquiera de esta lista de incorporaciones especiales retendrán los valores asignados en el shell actual:

break : continue . eval exec exit export 
readonly return set shift times trap unset

Esto sucederá si un shell funciona según la especificación POSIX.

Conclusión:

Es posible establecer variables para un solo comando, cualquier comando, asegurándose de que el comando no esté integrado de manera especial. El comando commandes una construcción regular. Solo le dice al shell que use un comando, no una función. Esta línea funciona en todos los shells (excepto ksh93):

$ unset a b c d; a=b c=d command eval 'f=g'; echo "<$a::$c::$f>"
<::::g>

En tal caso, los vars ayb se establecen para el entorno del comando de comando y se descartan después de eso.

En cambio, esto retendrá los valores asignados (excepto bash y zsh):

$ unset a b c d; a=b c=d eval 'f=g'; echo "<$a::$c::$f>"
<b::d::g>

Tenga en cuenta que la asignación después de eval está entre comillas simples para protegerla de expansiones no deseadas.

Entonces: Para colocar variables en el entorno del comando, use command eval:


fuente