Як я можу неекспортувати змінну, не втрачаючи її значення?


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Команда - лише приклад. Візьміть будь-яку команду, в межах якої ви не можете скинути змінну замість неї.
muru

Відповіді:


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встановлено, навіть якщо порожнє, і нічого іншого.
muru

Скажіть, чи є у вас коментарі typeset +xпроти export -nоболонок, які підтримують колишній? Чи export -nрідше, чи не зберігає якихось властивостей?
muru

@muru Якщо ви пишете баш-скрипт, ви можете використовувати його export -nабо typeset +xбайдуже. У ksh або zsh є тільки typeset +x.
Жил "ТАК - перестань бути злим"

7

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

-nВаріант exportвидаляє exportвластивість з кожного імені. (Див help export.)

Тому для bash, команда , яку ви хочете їсти:export -n foo


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 shell не встановлено, посилання на нього не розширюватиметься при +розширенні параметра. Але якщо він встановлений - незалежно від його значення - розширення параметра типу: ${parameter+word}розшириться на word-, а не на значення змінної. І так змінні оболонки самоперевіряються та самозамінюються на успіх.

Вони також можуть зазнати невдач . У функції верхньої , якщо погане ім'я знайдене я рухаюся $1в $2і відпустці $1порожній , тому що наступна річ , яку я роблю, або returnуспіх , якщо всі аргументи були оброблені і петля на кінці, або, якщо аргумент є недійсним, то оболонка розширення, $2в $1:?яке буде вбито сценарій оболонки і повернеться переривання в інтерактивний при написанні wordв stderr.

У другому getoptsвиконують завдання. І це не призначить поганого імені - скоріше напишіть, що воно випише стандартне повідомлення про помилку в stderr. Більше того, це зберігає значення arg, $OPTARG якщо аргументом в першу чергу було ім'я змінної набору. Отже після виконання getoptsвсього необхідного є evalрозширення набору OPTARGу відповідне завдання.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.