¿Cómo puedo anular la exportación de una variable sin perder su valor?

10

Digamos que exporté una variable:

foo=bar
export foo

Ahora, me gustaría desexportarlo. Es decir, si lo hago, sh -c 'echo "$foo"'no debería hacerlo bar. foono debería aparecer en sh -cel entorno del todo. sh -ces simplemente un ejemplo, una manera fácil de mostrar la presencia de una variable. El comando podría ser cualquier cosa, puede ser algo cuyo comportamiento se ve afectado simplemente por la presencia de la variable en su entorno.

Yo puedo:

  1. unset la variable, y la pierdes
  2. Elimínelo usando envpara cada comando:env -u foo sh -c 'echo "$foo"'
    • poco práctico si desea continuar usando el shell actual por un tiempo.

Idealmente, me gustaría mantener el valor de la variable, pero que no se muestre en absoluto en un proceso secundario, ni siquiera como una variable vacía.

Supongo que podría hacer:

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

Esto corre el riesgo de pisotear otherfoo, si ya existe.

¿Es esa la única forma? ¿Hay alguna forma estándar?

muru
fuente
1
Se podía eco del valor en un archivo temporal, utilizando mktempsi es que es lo suficientemente portátil, y desarmar el valor, y la fuente del archivo temporal para asignar la variable. Al menos se puede crear un archivo temporal con un nombre más o menos arbitrario en contraste con una variable de shell.
Thomas Dickey
@Sukminder El sh -ccomando es simplemente un ejemplo. Tome cualquier comando dentro del cual no pueda desarmar una variable en su lugar, si lo desea.
muru

Respuestas:

6

No hay una forma estándar.

Puede evitar usar una variable temporal usando una función. La siguiente función se encarga de mantener las variables no establecidas y las variables vacías vacías. Sin embargo, no admite características que se encuentran en algunos shells, como variables de solo lectura o de tipo.

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

En ksh, bash y zsh, puede exportar una variable con typeset +x foo. Esto conserva propiedades especiales como los tipos, por lo que es preferible usarlo. Creo que todos los proyectiles que tienen typesetincorporado tienen typeset +x.

case $(LC_ALL=C type typeset 2>&1) in
  typeset\ *\ builtin) unexport () { typeset +x -- "$@"; };;
  *) unexport () {  };; # code above
esac
Gilles 'SO- deja de ser malvado'
fuente
1
Para aquellos que no están familiarizados ${var+foo}, evalúa foosi varestá configurado, incluso si está vacío, y nada de lo contrario.
Muru
Diga, ¿tiene algún comentario sobre typeset +xvs export -npara los shells que admiten el primero? ¿Es export -nmás raro o no conserva algunas propiedades?
muru
@muru Si estás escribiendo un script bash, puedes usarlo export -no typeset +xindiferentemente. En ksh o zsh, solo hay typeset +x.
Gilles 'SO- deja de ser malvado'
7

EDIT: Para bashúnica, como se ha señalado en los comentarios:

La -nopción para exporteliminar la exportpropiedad de cada nombre de pila. (Ver help export)

Así que para bash, el comando que desea es:export -n foo

Comodín
fuente
1
Eso es específico del shell (ver POSIX ), OP no especificó un shell, pero pidió una forma estándar de resolver el problema.
Thomas Dickey
1
@ThomasDickey, no estaba al tanto de eso. Gracias, actualizado.
Comodín el
3

Escribí una función POSIX similar, pero esto no arriesga la ejecución de código arbitrario:

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

También manejará tantos argumentos como desee para proporcionarlo. Si un argumento es un nombre válido que de otro modo no está establecido, se ignora en silencio. Si un argumento es un nombre incorrecto, escribe en stderr y se detiene según corresponda, aunque cualquier nombre válido que precede a un inválido en su línea de comando aún se procesará.

Pensé en otra forma. Me gusta mucho mejor.

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

Bueno, ambos usan muchas de las mismas técnicas. Básicamente, si un shell var está desarmado, una referencia a él no se expandirá con una +expansión de parámetros. Pero si se establece, independientemente de su valor, una expansión de parámetros como: ${parameter+word}se expandirá a word, y no al valor de la variable. Y así, las variables de shell se autoevalúan y sustituyen automáticamente en caso de éxito.

También pueden fallar a sí mismos . En la función de la parte superior si un nombre mal se encuentra muevo $1en $2y la licencia $1nula debido a que el siguiente que hago es o bien returnel éxito si todos los argumentos han sido procesados y el bucle llega a su fin, o, si el arg no era válida, el shell ampliar la $2en $1:?que matará a una concha con guión y devolver una interrupción de un interactivo al escribir worden stderr.

En el segundo getoptshace las tareas. Y no asignará un nombre incorrecto, más bien escribirlo escribirá un mensaje de error estándar para stderr. Además, guarda el valor del argumento $OPTARG si el argumento era el nombre de una variable establecida en primer lugar. Entonces, después de hacer getoptstodo lo que se necesita es la expansión de evalun conjunto OPTARGen la asignación apropiada.

mikeserv
fuente
2
Uno de estos días, estaré en la sala de psiquiatría en algún lugar después de tratar de entender una de sus respuestas. : D ¿Cómo sufre la otra respuesta la ejecución de código arbitrario? ¿Puede dar un ejemplo?
Muru
3
@muru: no, a menos que un argumento sea un nombre no válido. pero ese no es el problema: el problema es que la entrada no está validada. Sí, es necesario que le pase un argumento con un nombre extraño para que ejecute código arbitrario, pero esa es la base de cada CVE en la historia. Si intentas exportun nombre extraño, no matará tu computadora.
mikeserv
1
@muru - oh, y los argumentos pueden ser arbitrarios: var=something; varname=var; export "$varname"es perfectamente válido. lo mismo ocurre unsetcon esto y con lo otro, pero en el momento en que el contenido de esa "$varname"variable se vuelve loco, puede ser lamentable. y así es como bashocurrió toda la debacle de exportación de funciones.
mikeserv
1
@mikeserv, creo que obtendría muchos más votos a favor (al menos de mí) si reemplazara ese código ofuscado con un código que se explica por sí mismo (o al menos comentó las líneas)
PSkocik
1
@PSkocik - hecho.
mikeserv