Comment puis-je dés-exporter une variable, sans perdre sa valeur?

10

Disons que j'ai exporté une variable:

foo=bar
export foo

Maintenant, je voudrais le désexporter. Cela veut dire que si je le fais, je ne sh -c 'echo "$foo"'devrais pas y arriver bar. foone devrait pas apparaître du tout dans sh -cl'environnement. sh -cn'est qu'un exemple, un moyen simple de montrer la présence d'une variable. La commande peut être n'importe quoi - elle peut être quelque chose dont le comportement est affecté simplement par la présence de la variable dans son environnement.

Je peux:

  1. unset la variable, et la perdre
  2. Supprimez-le en utilisant envpour chaque commande:env -u foo sh -c 'echo "$foo"'
    • peu pratique si vous souhaitez continuer à utiliser le shell actuel pendant un certain temps.

Idéalement, je voudrais conserver la valeur de la variable, mais ne pas la montrer du tout dans un processus enfant, pas même en tant que variable vide.

Je suppose que je pourrais faire:

otherfoo="$foo"; unset foo; foo="$otherfoo"; unset otherfoo

Cela risque de piétiner otherfoo, s'il existe déjà.

Est-ce le seul moyen? Existe-t-il des moyens standard?

muru
la source
1
Vous pouvez faire écho à la valeur dans un fichier temporaire, en utilisant mktempsi cela est suffisamment portable, annuler la valeur et source le fichier temporaire pour affecter la variable. Au moins un fichier temporaire peut être créé avec un nom plus ou moins arbitraire contrairement à une variable shell.
Thomas Dickey
@Sukminder La sh -ccommande n'est qu'un exemple. Prenez n'importe quelle commande dans laquelle vous ne pouvez pas annuler une variable à sa place, si vous voulez.
muru

Réponses:

6

Il n'y a pas de moyen standard.

Vous pouvez éviter d'utiliser une variable temporaire en utilisant une fonction. La fonction suivante prend soin de garder les variables non définies non définies et les variables vides vides. Il ne prend cependant pas en charge les fonctionnalités trouvées dans certains shells telles que les variables en lecture seule ou typées.

unexport () {
  while [ "$#" -ne 0 ]; do
    eval "set -- \"\${$1}\" \"\${$1+set}\" \"\$@\""
    if [ -n "$2" ]; then
      unset "$3"
      eval "$3=\$1"
    fi
    shift; shift; shift
  done
}
unexport foo bar

Dans ksh, bash et zsh, vous pouvez annuler l'exportation d'une variable avec typeset +x foo. Cela préserve les propriétés spéciales telles que les types, il est donc préférable de l'utiliser. Je pense que tous les obus qui ont un typesetintégré ont typeset +x.

case $(LC_ALL=C type typeset 2>&1) in
  typeset\ *\ builtin) unexport () { typeset +x -- "$@"; };;
  *) unexport () {  };; # code above
esac
Gilles 'SO- arrête d'être méchant'
la source
1
Pour ceux qui ne le connaissent pas ${var+foo}, il évalue foosi varest défini, même s'il est vide, et rien d'autre.
muru
Dites, avez-vous des commentaires sur typeset +xvs export -npour les shells qui supportent les premiers? Est-ce export -nplus rare ou ne conserve-t-il pas certaines propriétés?
muru
@muru Si vous écrivez un script bash, vous pouvez utiliser export -nou typeset +xindifféremment. En ksh ou zsh, il n'y en a que typeset +x.
Gilles 'SO- arrête d'être méchant'
7

EDIT: Pour bashseulement, comme indiqué dans les commentaires:

L' -noption de exportsuppression de la exportpropriété de chaque nom donné. (Voir help export.)

Donc pour bash, la commande que vous voulez est:export -n foo

Caractère générique
la source
1
C'est spécifique au shell (voir POSIX ), OP n'a pas spécifié de shell, mais a demandé un moyen standard de résoudre le problème.
Thomas Dickey
1
@ThomasDickey, n'était pas au courant de cela. Merci, mis à jour.
Wildcard
3

J'ai écrit une fonction POSIX similaire, mais cela ne risque pas d'exécuter du code arbitraire:

unexport()
    while case ${1##[0-9]*} in                   ### rule out leading numerics
          (*[!_[:alnum:]]*|"")                   ### filter out bad|empty names
          set "" ${1+"bad name: '$1'"}           ### prep bad name error
          return ${2+${1:?"$2"}}                 ### fail w/ above err or return 
          esac
    do    eval  set '"$'"{$1+$1}"'" "$'"$1"'" "$'@\" ###  $1 = (  $1+ ? $1 : "" )
          eval  "${1:+unset $1;$1=\$2;} shift 3"     ### $$1 = ( $1:+ ? $2 : -- )
    done

Il traitera également autant d'arguments que vous voudrez le fournir. Si un argument est un nom valide qui n'est pas déjà défini autrement, il est ignoré en silence. Si un argument est un mauvais nom, il écrit dans stderr et s'arrête comme il convient, bien que tout nom valide précédant un invalide sur sa ligne de commande soit toujours traité.

J'ai pensé à une autre façon. Je l'aime beaucoup mieux.

unexport()
        while   unset OPTARG; OPTIND=1           ### always work w/ $1
                case  ${1##[0-9]*}    in         ### same old same old
                (*[!_[:alnum:]]*|"")             ### goodname && $# > 0 || break
                    ${1+"getopts"} : "$1"        ### $# ? getopts : ":"
                    return                       ### getopts errored or ":" didnt
                esac
        do      eval   getopts :s: '"$1" -"${'"$1+s}-\$$1\""
                eval   unset  "$1;  ${OPTARG+$1=\${OPTARG}#-}"
                shift
        done

Eh bien, les deux utilisent beaucoup des mêmes techniques. Fondamentalement, si une var shell n'est pas définie, une référence à celle-ci ne se développera pas avec une +expansion de paramètre. Mais s'il est défini - quelle que soit sa valeur - une expansion de paramètre comme: ${parameter+word}s'étendra à word- et non à la valeur de la variable. Et donc les variables shell s'auto-testent et se remplacent elles-mêmes en cas de succès.

Ils peuvent également échouer . Dans la fonction supérieure, si un mauvais nom est trouvé, je passe $1dans $2et laisse $1null parce que la prochaine chose que je fais est soit le returnsuccès si tous les arguments ont été traités et la boucle est terminée, soit, si l'argument n'était pas valide, le shell développez le $2dans $1:?lequel va tuer un shell scripté et retourner une interruption à un interactif lors de l'écriture wordsur stderr.

Dans le second, getoptsles affectations sont effectuées. Et il n'attribuera pas de mauvais nom - plutôt, il écrira un message d'erreur standard à stderr. De plus, il enregistre la valeur de l'argument $OPTARG si l'argument était le nom d'une variable définie en premier lieu. Donc, après avoir fait getoptstout ce qui est nécessaire, il faut développer evalun ensemble OPTARGdans l'affectation appropriée.

mikeserv
la source
2
Un de ces jours, je serai dans le service psychiatrique quelque part après avoir essayé d'envelopper ma tête autour d'une de vos réponses. : D Comment l'autre réponse souffre-t-elle de l'exécution de code arbitraire? Pouvez vous donner un exemple?
muru
3
@muru - pas sauf si un argument n'est pas un nom valide. mais ce n'est pas le problème - le problème est que l'entrée n'est pas validée. oui, il est nécessaire que vous lui passiez un argument avec un nom étrange pour lui faire exécuter du code arbitraire - mais c'est à peu près la base de chaque CVE dans l'histoire. si vous essayez exportun nom étrange, cela ne tuera pas votre ordinateur.
mikeserv
1
@muru - oh, et les arguments peuvent être arbitraires: var=something; varname=var; export "$varname"est parfaitement valide. il en va de même pour unsetet avec ceci et avec l'autre, mais à la minute où le contenu de cette "$varname"variable devient fou, cela pourrait être regrettable. et c'est à peu près de cette façon que cette bashdébâcle d'exportation de fonctions s'est produite.
mikeserv
1
@mikeserv je pense que vous obtiendrez beaucoup plus de votes positifs (au moins de ma part) si vous remplaciez ce code obscurci par un code qui s'explique (ou commentait les lignes, au moins)
PSkocik
1
@PSkocik - terminé.
mikeserv