是否有一种符合 POSIX 标准的方法来将变量的范围限制为声明它的函数?IE:
Testing()
{
TEST="testing"
}
Testing
echo "Test is: $TEST"
应该打印“测试是:”。我已经阅读了有关 declare、local 和 typeset 关键字的信息,但看起来它们不是 POSIX 内置必需的。
它通常使用local
关键字完成,如您所知,POSIX 未定义该关键字。这是关于将 'local' 添加到 POSIX的内容丰富的讨论。
然而,即使是我所知道的最原始的兼容 POSIX 的 shell,它也被某些 GNU/Linux 发行版用作/bin/sh
默认值,dash
(Debian Almquist Shell)也支持它。FreeBSD 和 NetBSD 使用ash
原始的 Almquist Shell,它也支持它。OpenBSD 使用一个也支持它的ksh
实现。/bin/sh
因此,除非您的目标是支持非 GNU 非 BSD 系统,如 Solaris,或使用标准 ksh 等的系统,否则您可以使用local
. (可能想在脚本开头的 shebang 行下方添加一些注释,注意它不是严格意义上的 POSIX sh 脚本。只是为了不作恶。)说了这么多,您可能想检查相应的所有这些sh
支持的实现的手册页local
,因为它们的工作方式可能存在细微差别。或者只是不使用local
:
如果您真的想完全符合 POSIX,或者不想弄乱可能的问题,因此不使用local
,那么您有几个选择。Lars Brinkhoff 给出的答案是合理的,您可以将函数包装在子外壳中。不过,这可能会产生其他不良影响。顺便说一句,shell 语法(每个 POSIX)允许以下内容:
my_function()
(
# Already in a sub-shell here,
# I'm using ( and ) for the function's body and not { and }.
)
虽然可以避免这种超级便携,但一些旧的 Bourne shell 甚至可能不兼容 POSIX。只是想提一下POSIX允许它。
另一种选择是unset
在函数体末尾使用变量,但这当然不会恢复旧值,所以我猜这并不是你想要的,它只会防止变量的函数内值泄漏到外面。我猜不是很有用。
我能想到的最后一个疯狂的想法是实现local
自己。shell 有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's 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 委员会的成员看,不要给伯恩先生看,也许给麦卡锡父亲的鬼魂看,让他笑一笑。你被警告了,你没有从我这里学到。
编辑:
显然我被打败了,greybot
在 Freenode(属于#bash
)上发送 IRC 机器人命令“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 "$@"'
fi
}
此成绩单演示了用法:
$ x=a
$ y=b
$ f
test1 test2
$ echo $x $y
a b
因此,它允许在 if 表单的分支中使用变量x
和y
本地变量。then
可以在else
分支处添加更多变量;请注意,必须将它们添加两次,一次像variable=
在初始列表中一样,一次作为参数传递给typeset
. 请注意,不需要unlet
左右(它是一个“透明”的实现),并且没有进行名称修改和过度eval
操作。因此,总体而言,它似乎是一个更清洁的实现。
编辑2:
POSIX 没有定义出来typeset
,并且 Almquist Shell(FreeBSD、NetBSD、Debian)的实现不支持它。所以上面的hack在这些平台上不起作用。
我相信最接近的是将函数体放在子shell中。
例如试试这个
foo()
{
( x=43 ; echo $x )
}
x=42
echo $x
foo
echo $x
这实际上内置于 POSIX 函数声明的设计中。
如果您希望在父作用域中声明 的变量可以在函数中访问,但在父作用域中保留其值不变,只需:
*使用显式子shell声明您的函数,即使用
subshell_function()
(with parentheses)
,不是inline_function()
{ with braces ;}
内联分组与子shell 分组的行为在整个语言中是一致的。
如果您想“混合搭配”,请从内联函数开始,然后根据需要嵌套子外壳函数。它很笨重,但很有效。
这是一个启用范围的函数:
scope() {
eval "$(set)" command eval '\"\$@\"'
}
示例脚本:
x() {
y='in x'
echo "$y"
}
y='outside x'
echo "$y"
scope x
echo "$y"
结果:
outside x
in x
outside x
如果你想和我一起下地狱,我已经对这个eval
概念做了更详细的实现。
这会自动记录您的准作用域变量,可以使用更熟悉的语法调用,并在离开嵌套作用域时正确取消设置(而不是仅仅为空)变量。
如您所见,您push_scope
进入一个范围,_local
声明您的准局部变量,并pop_scope
离开一个范围。用于_unset
取消设置变量,并pop_scope
在您再次退出该范围时重新取消设置。
your_func() {
push_scope
_local x="baby" y="you" z
x="can"
y="have"
z="whatever"
_unset z
push_scope
_local x="you"
_local y="like"
pop_scope
pop_scope
}
所有乱码的变量名称后缀都是为了防止名称冲突而格外安全。
# Simulate entering of a nested variable scope
# To be used in conjunction with push_scope(), pop_scope(), and _local()
push_scope() {
SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D=$(( $SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D + 1 ))
}
# Store the present value of the specified variable(s), allowing use in a new scope.
# To be used in conjunction with push_scope(), pop_scope(), and _local()
#
# Parameters:
# $@ : string; name of variable to store the value of
scope_var() {
for varname_FB94CFD263CF11E89500036F7F345232 in "${@}"; do
eval "active_varnames_FB94CFD263CF11E89500036F7F345232=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}\""
# echo "Active varnames: ${active_varnames_FB94CFD263CF11E89500036F7F345232}"
case " ${active_varnames_FB94CFD263CF11E89500036F7F345232} " in
*" ${varname_FB94CFD263CF11E89500036F7F345232} "* )
# This variable was already stored in a previous call
# in the same scope. Do not store again.
# echo "Push \${varname_FB94CFD263CF11E89500036F7F345232}, but already stored."
:
;;
* )
if eval "[ -n \"\${${varname_FB94CFD263CF11E89500036F7F345232}+x}\" ]"; then
# Store the existing value from the previous scope.
# Only variables that were set (including set-but-empty) are stored
# echo "Pushing value of \$${varname_FB94CFD263CF11E89500036F7F345232}"
eval "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_FB94CFD263CF11E89500036F7F345232}=\"\${${varname_FB94CFD263CF11E89500036F7F345232}}\""
else
# Variable is unset. Do not store the value; an unstored
# value will be used to indicate its unset state. The
# variable name will still be registered.
# echo "Not pushing value of \$${varname_FB94CFD263CF11E89500036F7F345232}; was previously unset."
:
fi
# Add to list of variables managed in this scope.
# List of variable names is space-delimited.
eval "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}${varname_FB94CFD263CF11E89500036F7F345232} \""
;;
esac
unset active_varnames_FB94CFD263CF11E89500036F7F345232
done
unset varname_FB94CFD263CF11E89500036F7F345232
}
# Simulate declaration of a local variable
# To be used in conjunction with push_scope(), pop_scope(), and _local()
#
# This function is a convenience wrapper over scope_var().
#
# Can be called just like the local keyword.
# Example usage: _local foo="foofoofoo" bar="barbarbar" qux qaz=""
_local() {
for varcouple_44D4987063D111E8A46923403DDBE0C7 in "${@}"; do
# Example string: foo="barbarbar"
varname_44D4987063D111E8A46923403DDBE0C7="${varcouple_44D4987063D111E8A46923403DDBE0C7%%=*}"
varvalue_44D4987063D111E8A46923403DDBE0C7="${varcouple_44D4987063D111E8A46923403DDBE0C7#*=}"
varvalue_44D4987063D111E8A46923403DDBE0C7="${varvalue_44D4987063D111E8A46923403DDBE0C7#${varcouple_44D4987063D111E8A46923403DDBE0C7}}"
# Store the value for the previous scope.
scope_var "${varname_44D4987063D111E8A46923403DDBE0C7}"
# Set the value for this scope.
eval "${varname_44D4987063D111E8A46923403DDBE0C7}=\"\${varvalue_44D4987063D111E8A46923403DDBE0C7}\""
unset varname_44D4987063D111E8A46923403DDBE0C7
unset varvalue_44D4987063D111E8A46923403DDBE0C7
unset active_varnames_44D4987063D111E8A46923403DDBE0C7
done
unset varcouple_44D4987063D111E8A46923403DDBE0C7
}
# Simulate unsetting a local variable.
#
# This function is a convenience wrapper over scope_var().
#
# Can be called just like the unset keyword.
# Example usage: _unset foo bar qux
_unset() {
for varname_6E40DA2E63D211E88CE68BFA58FE2BCA in "${@}"; do
scope_var "${varname_6E40DA2E63D211E88CE68BFA58FE2BCA}"
unset "${varname_6E40DA2E63D211E88CE68BFA58FE2BCA}"
done
}
# Simulate exiting out of a nested variable scope
# To be used in conjunction with push_scope(), pop_scope(), and _local()
pop_scope() {
eval "varnames_2581E94263D011E88919B3D175643B87=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}\""
# Cannot iterate over $varnames by setting $IFS; $IFS does not work
# properly on zsh. Workaround using string manipulation.
while [ -n "${varnames_2581E94263D011E88919B3D175643B87}" ]; do
# Strip enclosing spaces from $varnames.
while true; do
varnames_old_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87}"
varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87# }"
varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87% }"
if [ "${varnames_2581E94263D011E88919B3D175643B87}" = "${varnames_2581E94263D011E88919B3D175643B87}" ]; then
break
fi
done
# Extract the variable name for the current iteration and delete it from the queue.
varname_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87%% *}"
varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87#${varname_2581E94263D011E88919B3D175643B87}}"
# echo "pop_scope() iteration on \$SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}"
# echo "varname: ${varname_2581E94263D011E88919B3D175643B87}"
if eval "[ -n \""\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}+x}"\" ]"; then
# echo "Value found. Restoring value from previous scope."
# echo eval "${varname_2581E94263D011E88919B3D175643B87}=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}}\""
eval "${varname_2581E94263D011E88919B3D175643B87}=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}}\""
unset "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}"
else
# echo "Unsetting \$${varname_2581E94263D011E88919B3D175643B87}"
unset "${varname_2581E94263D011E88919B3D175643B87}"
fi
# Variable cleanup.
unset varnames_old_2581E94263D011E88919B3D175643B87
done
unset SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES
unset varname_2581E94263D011E88919B3D175643B87
unset varnames_2581E94263D011E88919B3D175643B87
SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D=$(( $SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D - 1 ))
}
可以使用一小组通用函数在 Posix Shell 中模拟局部变量。
下面的示例代码演示了两个函数,称为 Local 和 EndLocal,它们可以解决问题。
所有函数都很短,并且使用描述性名称,因此它们应该相对容易理解。
它们支持带有诸如空格、单引号和双引号等复杂字符的变量。
注意:它们使用以 LOCAL_ 开头的全局变量,因此与现有同名变量发生冲突的风险很小。
Test 例程递归调用自身 3 次,并修改了一些局部和全局变量。
输出显示 A 和 B 局部变量被保留,与全局 N 变量相反。
代码:
#!/bin/sh
#-----------------------------------------------------------------------------#
# Manage pseudo-local variables in a Posix Shell
# Check if a variable exists.
VarExists() { # $1=Variable name
eval "test \"\${${1}+true}\" = \"true\""
}
# Get the value of a variable.
VarValue() { # $1=Variable name
eval "echo \"\${$1}\""
}
# Escape a string within single quotes, for reparsing by eval
SingleQuote() { # $1=Value
echo "$1" | sed -e "s/'/'\"'\"'/g" -e "s/.*/'&'/"
}
# Set the value of a variable.
SetVar() { # $1=Variable name; $2=New value
eval "$1=$(SingleQuote "$2")"
}
# Emulate local variables
LOCAL_SCOPE=0
Local() { # $*=Local variables names
LOCAL_SCOPE=$(expr $LOCAL_SCOPE + 1)
SetVar "LOCAL_${LOCAL_SCOPE}_VARS" "$*"
for LOCAL_VAR in $* ; do
if VarExists $LOCAL_VAR ; then
SetVar "LOCAL_${LOCAL_SCOPE}_RESTORE_$LOCAL_VAR" "SetVar $LOCAL_VAR $(SingleQuote "$(VarValue $LOCAL_VAR)")"
else
SetVar "LOCAL_${LOCAL_SCOPE}_RESTORE_$LOCAL_VAR" "unset $LOCAL_VAR"
fi
done
}
# Restore the initial variables
EndLocal() {
LOCAL_RETCODE=$?
for LOCAL_VAR in $(VarValue "LOCAL_${LOCAL_SCOPE}_VARS") ; do
eval $(VarValue "LOCAL_${LOCAL_SCOPE}_RESTORE_$LOCAL_VAR")
unset "LOCAL_${LOCAL_SCOPE}_RESTORE_$LOCAL_VAR"
done
unset "LOCAL_${LOCAL_SCOPE}_VARS"
LOCAL_SCOPE=$(expr $LOCAL_SCOPE - 1)
return $LOCAL_RETCODE
}
#-----------------------------------------------------------------------------#
# Test routine
N=3
Test() {
Local A B
A=Before
B=$N
echo "#1 N=$N A='$A' B=$B"
if [ $N -gt 0 ] ; then
N=$(expr $N - 1)
Test
fi
echo "#2 N=$N A='$A' B=$B"
A="After "
echo "#3 N=$N A='$A' B=$B"
EndLocal
}
A="Initial value"
Test
echo "#0 N=$N A='$A' B=$B"
输出:
larvoire@JFLZB:/tmp$ ./LocalVars.sh
#1 N=3 A='Before' B=3
#1 N=2 A='Before' B=2
#1 N=1 A='Before' B=1
#1 N=0 A='Before' B=0
#2 N=0 A='Before' B=0
#3 N=0 A='After ' B=0
#2 N=0 A='Before' B=1
#3 N=0 A='After ' B=1
#2 N=0 A='Before' B=2
#3 N=0 A='After ' B=2
#2 N=0 A='Before' B=3
#3 N=0 A='After ' B=3
#0 N=0 A='Initial value' B=
larvoire@JFLZB:/tmp$
使用相同的技术,我认为应该可以动态检测是否支持 local 关键字,如果不支持,则定义一个名为 local 的新函数来模拟它。
这样,在具有内置本地变量的现代 shell 的正常情况下,性能会好得多。没有它,事情仍然可以在旧的 Posix shell 上运行。
实际上我们需要三个动态生成的函数:
function myfunc {
使用语法定义函数,并用于typeset myvar
定义变量。如果您以这种方式定义函数而不是使用myfunc(){
语法,那么所有常见的 Bourne shell(bash、zsh、ksh '88 和 '93)都将本地化定义为的变量typeset
(以及排版的别名,如integer
)。
或者重新发明轮子。无论哪个漂浮你的船。;)
编辑:虽然问题要求使用 POSIX,并且这不是符合 POSIX 的函数定义语法,但询问的人在稍后的评论中表示他正在使用 bash。将“typset”与替代函数定义语法结合使用是最好的解决方案,因为真正的 POSIX 机制需要分叉新子外壳的额外开销。