Как я могу отменить экспорт переменной без потери ее значения?

10

Допустим, я экспортировал переменную:

foo=bar
export foo

Теперь я хотел бы отменить экспорт. То есть, если я это сделаю, sh -c 'echo "$foo"'я не должен получить bar. fooвообще не должен появляться в sh -cроссийской среде. sh -cэто просто пример, простой способ показать наличие переменной. Командой может быть что угодно - это может быть что-то, на чье поведение влияет просто наличие переменной в ее окружении.

Я могу:

  1. unset переменная, и потерять ее
  2. Удалите его, используя envдля каждой команды:env -u foo sh -c 'echo "$foo"'
    • нецелесообразно, если вы хотите продолжить использовать текущую оболочку на некоторое время.

В идеале я хотел бы сохранить значение переменной, но не показывать его вообще в дочернем процессе, даже в виде пустой переменной.

Я думаю, я мог бы сделать:

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

Это может растоптать otherfoo, если оно уже существует.

Это единственный способ? Есть ли стандартные способы?

Мур
источник
1
Вы можете повторить значение во временный файл, используя , mktempесли что является портативным достаточно, и сбросить значение, и источник временный файл для присвоения переменной. По крайней мере временный файл может быть создан с более или менее произвольным именем в отличие от переменной оболочки.
Томас Дики
@Sukminder Команда sh -cявляется просто примером. Возьмите любую команду, в рамках которой вы не можете удалить переменную вместо нее, если хотите.
Муру

Ответы:

6

Там нет стандартного способа.

Вы можете избежать использования временной переменной с помощью функции. Следующая функция заботится о том, чтобы неустановленные переменные оставались неустановленными, а пустые переменные оставались пустыми. Однако он не поддерживает функции, присутствующие в некоторых оболочках, такие как переменные только для чтения или типизированные переменные.

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

В ksh, bash и zsh вы можете удалить переменную с помощью typeset +x foo. Это сохраняет специальные свойства, такие как типы, поэтому предпочтительно использовать его. Я думаю, что все оболочки, которые имеют typesetвстроенные, имеют typeset +x.

case $(LC_ALL=C type typeset 2>&1) in
  typeset\ *\ builtin) unexport () { typeset +x -- "$@"; };;
  *) unexport () {  };; # code above
esac
Жиль "ТАК - прекрати быть злым"
источник
1
Для тех, кто не знаком с ним ${var+foo}, он оценивает, fooесли varустановлено, даже если оно пустое, и ничего другого.
Муру
Скажем, у вас есть какие - либо комментарии по typeset +xсравнению export -nдля оболочек , которые делают поддержку бывшего? Является ли export -nреже, или же она не сохраняет некоторые свойства?
Муру
@muru Если вы пишете скрипт bash, вы можете использовать его export -nили typeset +xбезразлично. В кш или зш есть только typeset +x.
Жиль "ТАК ... перестать быть злым"
7

EDIT: Для bashтолько, как указано в комментариях:

-nВариант exportудаляет exportсвойство из каждого имени. (См help export.)

Поэтому для bash, команда , которую вы хотите есть:export -n foo

Wildcard
источник
1
Это зависит от оболочки (см. POSIX ), OP не указал оболочку, но попросил стандартный способ решения проблемы.
Томас Дики
1
@ThomasDickey, не знал об этом. Спасибо, обновлено.
Wildcard
3

Я написал аналогичную функцию POSIX, но это не рискует выполнить произвольный код:

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

Он также будет обрабатывать столько аргументов, сколько вы пожелаете предоставить. Если аргумент является допустимым именем, которое в противном случае уже не задано, оно игнорируется. Если аргумент является неправильным именем, он записывает в stderr и останавливается соответствующим образом, хотя любое действительное имя, предшествующее недопустимому в его командной строке, все равно будет обработано.

Я думал о другом способе. Мне это нравится намного лучше.

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

Хорошо, оба из них используют много тех же самых методов. В основном, если оболочка var не установлена, ссылка на нее не будет расширяться с +расширением параметра. Но если он установлен - независимо от его значения - расширение параметра, подобное: ${parameter+word}будет расширяться до word-, а не до значения переменной. И так переменные оболочки самопроверка и подстановка на успех.

Они также могут потерпеть неудачу . В верхней функции, если найдено плохое имя, я вхожу $1в ноль $2и оставляю $1ноль, потому что следующее, что я делаю, это либо returnуспех, если все аргументы были обработаны и цикл заканчивается, либо, если аргумент был недействительным, оболочка будет разверните объект, $2в $1:?котором будет уничтожена сценарий оболочки и верните прерывание интерактивному при записи wordв stderr.

Во втором getoptsделает задания. И это не будет назначать плохое имя - скорее напишите, это напишет стандартное сообщение об ошибке в stderr. Более того, он сохраняет значение аргумента в том $OPTARG случае, если аргумент был именем переменной набора в первую очередь. Таким образом, после выполнения getoptsвсе, что необходимо, это расширение evalнабора OPTARGв соответствующее назначение.

mikeserv
источник
2
На днях я буду где-то в психиатрическом отделении после попытки обернуть голову вокруг одного из ваших ответов. : D Как другой ответ страдает от выполнения произвольного кода? Можете ли вы привести пример?
Муру
3
@muru - нет, если аргумент не является неверным именем. но это не проблема - проблема в том, что входные данные не проверены. да, требуется, чтобы вы передали ему аргумент со странным именем, чтобы он выполнял произвольный код - но это в значительной степени основа для каждого CVE в истории. Если вы попытаетесь exportиспользовать странное имя, оно не убьет ваш компьютер.
mikeserv
1
@muru - о, и аргументы могут быть произвольными: var=something; varname=var; export "$varname"совершенно верно. То же самое относится unsetи к этому, и к другому, но в ту минуту, когда содержимое этой "$varname"переменной сходит с ума, это может быть прискорбно. и это довольно, как все bashэто произошло с экспортом всей функции.
mikeserv
1
@mikeserv Я думаю, вы получите намного больше голосов (по крайней мере, от меня), если вы замените этот запутанный код кодом, который объясняет себя (или прокомментировал строки, по крайней мере)
PSkocik
1
@PSkocik - готово.
mikeserv