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

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

$ 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 

После нескольких тестов я узнал, что это происходит только в некоторых оболочках. Это происходит в тире, 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.

Как вы нашли, это поведение. Но это также имеет смысл.

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

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

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

Но они не являются единственными командами, которые сохраняют среду таким образом – функции тоже делают то же самое. И ошибки ведут себя по-разному для специальных встроенных – try 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" xyz x="$*" IFS=$1 shift echo "${x#"$IFS"}" "$*" 

 +x+y+zxyz 

…или…

 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! 

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

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

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

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

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

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

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

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

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

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

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

Здесь все, как обычно, ожидается.

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

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

  1. Переменные назначения, заданные с помощью специальных встроенных утилит, остаются в силе после завершения встроенных функций.
 $ sh -c 'unset abcd; a=bc=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 abcd; a=bc=d command eval 'f=g'; echo "<$a::$c::$f>" <::::g> 

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

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

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

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

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