Ответ 1
Обычно выполняется с ключевым словом local
, которое, как вам кажется, не определено POSIX. Вот информативное обсуждение о добавлении "local" в POSIX.
Однако даже самая примитивная POSIX-совместимая оболочка, о которой я знаю, которая используется некоторыми дистрибутивами GNU/Linux в качестве /bin/sh
по умолчанию, dash
(Debian Almquist Shell), поддерживает ее. FreeBSD и NetBSD используют ash
, оригинальную оболочку Almquist, которая также поддерживает ее. OpenBSD использует реализацию ksh
для /bin/sh
, которая также поддерживает его. Поэтому, если вы не нацелены на поддержку не-GNU-систем, отличных от BSD, таких как Solaris, или тех, которые используют стандартный ksh и т.д., Вы можете избежать использования local
. (Возможно, стоит поставить комментарий прямо в начале script, ниже строки shebang, отметив, что это не строго POSIX sh script. Просто чтобы не быть злым.) Сказав все это, вы можете захотеть для проверки соответствующих man-страниц всех этих реализаций sh
, поддерживающих local
, поскольку они могут иметь тонкие различия в том, как именно они работают. Или просто не используйте local
:
Если вы действительно хотите полностью соответствовать POSIX или не хотите вмешиваться в возможные проблемы, и поэтому не используйте local
, то у вас есть пара вариантов. Ответ, данный Ларсом Бринкхоффом, звучит, вы можете просто обернуть функцию в суб-оболочку. Однако это может иметь и другие нежелательные эффекты. Кстати, грамматика оболочки (для POSIX) позволяет:
my_function()
(
# Already in a sub-shell here,
# I'm using ( and ) for the function body and not { and }.
)
Хотя возможно, чтобы это было супер-портативным, некоторые старые оболочки Bourne могут быть даже не совместимы с POSIX. Просто хотел упомянуть, что POSIX позволяет это.
Другим вариантом будет unset
переменные в конце ваших тел функции, но это не собирается восстанавливать старое значение, конечно, так что это не совсем то, что вы хотите, я думаю, это просто предотвратит переменную, значение функции для утечки снаружи. Не очень полезно, я думаю.
Последняя и сумасшедшая идея, о которой я могу думать, - это реализовать local
самостоятельно. Оболочка имеет eval
, которая, каким бы злым она ни была, уступает некоторым безумным возможностям. Следующее в основном реализует динамическое масштабирование старых Lisps, я буду использовать ключевое слово let
вместо local
для дальнейших холодных точек, хотя в конце вы должны использовать так называемый unlet
:
# If you want you can add some error-checking and what-not to this. At present,
# wrong usage (e.g. passing a string with whitespace in it to `let', not
# balancing `let' and `unlet' calls for a variable, etc.) will probably yield
# very very confusing error messages or breakage. It also very dirty code, I
# just wrote it down pretty much at one go. Could clean up.
let()
{
dynvar_name=$1;
dynvar_value=$2;
dynvar_count_var=${dynvar_name}_dynvar_count
if [ "$(eval echo $dynvar_count_var)" ]
then
eval $dynvar_count_var='$(( $'$dynvar_count_var' + 1 ))'
else
eval $dynvar_count_var=0
fi
eval dynvar_oldval_var=${dynvar_name}_oldval_'$'$dynvar_count_var
eval $dynvar_oldval_var='$'$dynvar_name
eval $dynvar_name='$'dynvar_value
}
unlet()
for dynvar_name
do
dynvar_count_var=${dynvar_name}_dynvar_count
eval dynvar_oldval_var=${dynvar_name}_oldval_'$'$dynvar_count_var
eval $dynvar_name='$'$dynvar_oldval_var
eval unset $dynvar_oldval_var
eval $dynvar_count_var='$(( $'$dynvar_count_var' - 1 ))'
done
Теперь вы можете:
$ let foobar test_value_1
$ echo $foobar
test_value_1
$ let foobar test_value_2
$ echo $foobar
test_value_2
$ let foobar test_value_3
$ echo $foobar
test_value_3
$ unlet foobar
$ echo $foobar
test_value_2
$ unlet foobar
$ echo $foobar
test_value_1
(Кстати, unlet
может быть задано любое количество переменных сразу (как разные аргументы), для удобства, не показано выше.)
Не пробуйте это дома, не показывайте его детям, не показывайте его своим сотрудникам, не показывайте его на #bash
в Freenode, не показывайте его членам Комитет POSIX, не показывайте это мистеру Борну, возможно, покажите его отцу Маккарти, чтобы рассмешить его. Вы были предупреждены, и вы не узнали об этом от меня.
EDIT:
По-видимому, меня избили, отправив бот IRC greybot
на Freenode (принадлежит #bash
), команда "posixlocal" заставит дать один неясный код, который демонстрирует способ достижения локальных переменных в POSIX sh, Вот несколько очищенная версия, потому что оригинал был трудно расшифровать:
f()
{
if [ "$_called_f" ]
then
x=test1
y=test2
echo $x $y
else
_called_f=X x= y= command eval '{ typeset +x x y; } 2>/dev/null; f "[email protected]"'
fi
}
Эта транскрипция демонстрирует использование:
$ x=a
$ y=b
$ f
test1 test2
$ echo $x $y
a b
Таким образом, он позволяет использовать переменные x
и y
как локальные в ветки then
формы if. В ветки else
можно добавить больше переменных; обратите внимание, что их необходимо добавить дважды, как только variable=
в исходном списке, и один раз передан как аргумент typeset
. Обратите внимание, что не требуется unlet
или так (это "прозрачная" реализация), и никакие манипуляции с именами и чрезмерные eval
не выполняются. Таким образом, это, по-видимому, намного более чистая реализация.
ИЗМЕНИТЬ 2:
Выходит typeset
не определяется POSIX, а реализации оболочки Almquist (FreeBSD, NetBSD, Debian) не поддерживают его. Таким образом, вышеупомянутый хак не будет работать на этих платформах.