Les affectations variables affectent le shell en cours d'exécution

8

En écrivant du code, j'ai découvert que cette ligne:

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

Donne correctement l'heure réelle à "Los Angeles" et que la valeur de la variable TZn'est pas conservée. Tout comme il faut s'y attendre.

Cependant, avec cette ligne, que j'ai utilisée pour développer certains formats à ce jour, et qui exécute essentiellement la même chose, conserve la valeur de TZ:

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

Après plusieurs tests supplémentaires, j'ai découvert que cela ne se produit que dans certains obus. Cela se produit en dash, ksh mais pas en bash ou zsh.

Q's

Les questions sont les suivantes:

  • Pourquoi la valeur de TZ est-elle conservée dans la coque actuelle?
  • Comment cela pourrait-il être évité / contrôlé (si possible)?

Additionnel.

J'ai effectué des tests dans plusieurs shells avec ces deux lignes:

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"

Et cela se traduit:

/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 

La valeur TZ affecte le shell en cours d'exécution dans tous les shells sauf bash et zsh.


la source

Réponses:

6

Comme vous l'avez constaté, c'est un comportement spécifié. Mais cela a aussi du sens.

La valeur est conservée dans l'environnement du shell pour la même raison que la valeur des autres variables d'environnement sont conservées par d'autres commandes lorsque vous préfixez les définitions à leurs lignes de commande - vous définissez les variables dans leur environnement.

Les fonctions intégrées spéciales sont généralement la variété la plus intrinsèque dans n'importe quel shell - evalest essentiellement un nom accessible pour l'analyseur du shell, setsuit et configure les options du shell et les paramètres du shell, return/ break/ continuedéclenche le flux de contrôle de boucle, trapgère les signaux, execouvre / ferme les fichiers. Ce sont tous des utilitaires fondamentaux - et sont généralement mis en œuvre avec des emballages à peine présents sur la viande et les pommes de terre de votre coquille.

L'exécution de la plupart des commandes implique un environnement en couches - un environnement de sous - shell (qui ne doit pas nécessairement être un processus séparé) - que vous n'obtenez pas lorsque vous appelez les commandes spéciales. Ainsi, lorsque vous définissez l'environnement pour l'une de ces commandes, vous définissez l'environnement pour votre shell. Parce qu'ils représentent essentiellement votre coquille.

Mais ce ne sont pas les seules commandes qui conservent l'environnement de cette façon - les fonctions font de même également. Et les erreurs se comportent différemment pour les modules intégrés spéciaux - essayez cat <doesntexistpuis essayez exec <doesntexistou même juste : <doesntexistet pendant que la catcommande se plaindra, le execou :tuera un shell POSIX. Il en va de même pour les erreurs d'extension sur leur ligne de commande. Ils sont la boucle principale , en gros.

Ces commandes n'ont pas besoin de conserver l'environnement - certains shells enveloppent leurs composants internes plus étroitement que d'autres, exposent moins de fonctionnalités de base et ajoutent plus de tampon entre le programmeur et l'interface. Ces mêmes coquilles pourraient également avoir tendance à être un peu plus lentes que d'autres. Certainement, ils nécessitent de nombreux ajustements non standard pour les rendre conformes aux spécifications. Et de toute façon, ce n'est pas comme si c'était une mauvaise chose:

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

Ce truc est facile . Sinon, comment pourriez-vous conserver le retour de bad_commandsi simplement sans avoir à définir un tas d' environnement supplémentaire et encore faire des affectations conditionnellement?

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

Ce genre de truc fonctionne aussi. Les échanges sur place sont plus simples.

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

+x+y+z x y z

...ou...

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##*[\`\(]*}"

... est un autre que j'aime utiliser ...

echo kill my computer; haha! just kidding!
mikeserv
la source
@BinaryZebra - mais le fait est qu'ils ne fonctionnent pas différemment - lorsque vous définissez des variables pour une autre commande, elles persistent dans l'environnement de cet autre exécutable. lorsque vous définissez des variables dans l'environnement de votre shell, elles persistent également.
mikeserv
3

Il s'avère qu'il y a une raison très spécifique à ce comportement.
La description de ce qui se passe est un peu plus longue.

Uniquement les affectations.

Une ligne de commande composée (uniquement) d'affectations définira les variables de ce shell.

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

La valeur des vars attribués sera conservée.

Commande externe.

Affectations avant un ensemble de commandes externes de variables pour ce shell uniquement:

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

Et je veux dire "externe" comme toute commande qui doit être recherchée dans PATH.

Cela s'applique également aux fonctions intégrées normales (comme les cd, par exemple):

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

Jusqu'à présent, tout est comme prévu.

Intégrés spéciaux.

Mais pour les fonctions intégrées spéciales, POSIX nécessite que les valeurs soient définies pour ce shell .

  1. Les affectations de variables spécifiées avec des utilitaires intégrés spéciaux restent en vigueur après la fin de l'intégration.
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

J'utilise un appel pour shsupposer qu'il shs'agit d'un shell compatible POSIX.

Ce n'est pas quelque chose qui est habituellement utilisé.

Cela signifie que les affectations placées devant n'importe laquelle de cette liste de modules intégrés spéciaux doivent conserver les valeurs attribuées dans le shell en cours d'exécution:

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

Cela se produira si un shell fonctionne selon les spécifications POSIX.

Conclusion:

Il est possible de définir des variables pour une seule commande, n'importe quelle commande, en s'assurant que la commande n'est pas une fonction intégrée spéciale. La commande commandest une fonction intégrée standard. Il indique uniquement au shell d'utiliser une commande, pas une fonction. Cette ligne fonctionne dans tous les shells (sauf ksh93):

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

Dans ce cas, les variables a et b sont définies pour l'environnement de la commande command et supprimées après cela.

Au lieu de cela, cela conservera les valeurs attribuées (sauf bash et zsh):

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

Notez que l'affectation après eval est entre guillemets simples pour la protéger contre les expansions indésirables.

Donc: Pour placer des variables dans l'environnement de commande, utilisez command eval:


la source