Назначение переменных влияет на текущую работающую оболочку

8

При написании кода я обнаружил, что эта строка:

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

Правильно показывает фактическое время в «Лос-Анджелесе» и то, что значение переменной TZне сохраняется. Все как и следовало ожидать.

Однако с этой строкой, которую я использовал для расширения некоторых форматов до настоящего времени и которая по сути выполняет то же самое, сохраняется значение TZ:

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

После еще нескольких тестов я обнаружил, что это происходит только в некоторых оболочках. Это происходит в dash, ksh, но не в bash или zsh.

Q-х

Вопрос (ы):

  • Почему значение TZ сохраняется в настоящей оболочке?
  • Как этого можно избежать / контролировать (если это возможно)?

Дополнительно.

Я провел тесты в нескольких оболочках с этими двумя строками:

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"

И это приводит к:

/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 

Значение TZ влияет на работающую оболочку во всех оболочках, кроме bash и zsh.


источник

Ответы:

6

Как вы обнаружили, это специфическое поведение. Но это также имеет смысл.

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

В специальных встроенных функциях, как правило , наиболее свойственное разнообразие в любой оболочке - evalэто по существу доступное имя для синтаксического анализа командного интерпретатора, setотслеживает и конфигурирует оболочку опции и параметры оболочки, return/ break/ continueТриггер управление потоком петли, trapручки сигналы, execоткрывает / закрывают файлы. Все это фундаментальные утилиты - и обычно они реализуются с едва уловимыми обертками над мясом и картофелем вашей скорлупы.

Выполнение большинства команд требует некоторой многоуровневой среды - среды подоболочек (которая не обязательно должна быть отдельным процессом), которую вы не получите при вызове специальных встроенных функций. Поэтому, когда вы устанавливаете среду для одной из этих команд, вы устанавливаете среду для своей оболочки. Потому что они в основном представляют вашу оболочку.

Но это не единственные команды, которые сохраняют среду таким образом - функции делают то же самое. И ошибки ведут себя по-разному для специальных встроенных модулей - попробуйте cat <doesntexistи затем попробуйте exec <doesntexistили даже просто, : <doesntexistи пока catкоманда будет выдавать жалобу, команда execили :убьет оболочку POSIX. То же самое относится и к ошибкам расширения в их командной строке. Они основной цикл , в основном.

Этим командам не нужно сохранять среду - некоторые оболочки оборачивают свои внутренние компоненты более плотно, чем другие, раскрывают меньше основных функций и добавляют больше буфера между программистом и интерфейсом. Эти же снаряды могут быть немного медленнее других. Определенно, они требуют много нестандартных настроек, чтобы соответствовать спецификации. И в любом случае, это не так , как будто это плохо , что:

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

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

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

Такие вещи тоже работают. Поменяться местами проще.

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

+x+y+z x y z

...или...

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

... это еще один, который я люблю использовать ...

echo kill my computer; haha! just kidding!
mikeserv
источник
@BinaryZebra - но дело в том, что они не работают по-другому - когда вы устанавливаете переменные для какой-то другой команды, они сохраняются в среде этого другого исполняемого файла. когда вы устанавливаете переменные в среде вашей оболочки, они также сохраняются.
mikeserv
3

Оказывается, есть очень специфическая причина для такого поведения.
Описание того, что происходит, немного длиннее.

Только задания.

Командная строка, выполненная (только) из назначений, установит переменные для этой оболочки.

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

Значение присвоенных переменных будет сохранено.

Внешняя команда.

Назначения перед внешней командой устанавливают переменные только для этой оболочки:

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

И я имею в виду «внешнюю» как любую команду, которую нужно искать в PATH.

Это также относится к обычным встроенным модулям (например, cd):

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

До сих пор все как обычно ожидается.

Специальные встроенные модули.

Но для специальных встроенных функций POSIX требует, чтобы значения были установлены для этой оболочки .

  1. Переменные, заданные специальными встроенными утилитами, остаются в силе после их завершения.
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

Я использую вызов, чтобы shпредположить, что shэто POSIX-совместимая оболочка.

Это не то, что обычно используется.

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

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

Это произойдет, если оболочка работает согласно спецификации POSIX.

Вывод:

Можно установить переменные только для одной команды, любой команды, убедившись, что команда не является специальной встроенной. Команда commandявляется обычным встроенным. Он только говорит оболочке использовать команду, а не функцию. Эта строка работает во всех оболочках (кроме ksh93):

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

В таком случае переменные a и b устанавливаются для среды команды команды и после этого отбрасываются.

Вместо этого это сохранит присвоенные значения (кроме bash и zsh):

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

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

Итак: Для размещения переменных в командной среде используйте command eval:


источник