在shell脚本中将范围变量转换为函数的POSIX兼容方式 [英] POSIX-Compliant Way to Scope Variables to a Function in a Shell Script
问题描述
是否有符合POSIX标准的方法来将变量的范围限制为它声明的函数?即:
测试()
{
TEST =测试
}
测试
echoTest is:$ TEST
测试是:。我已阅读过关于declare,local和typeset的关键字,但它看起来并不像它们是必需的POSIX内置函数。
通常使用本地
关键字完成,这似乎并不是由POSIX定义的。以下是有关有关将POSIX添加到POSIX的讨论 。
然而,即使是最原始的符合POSIX标准的shell,我知道一些GNU / Linux发行版使用它作为 / bin / sh
默认, dash
(Debian Almquist Shell),支持它。 FreeBSD和NetBSD使用 ash
,原始的Almquist Shell也支持它。 OpenBSD对 / bin / sh
使用 ksh
实现,该实现也支持它。因此,除非您打算支持非GNU非BSD系统(如Solaris)或使用标准ksh等的系统,否则您可以使用 local
来避开。 (可能希望在脚本的开头,在shebang行下面注明一些注释,注意它不是严格意义上的POSIX sh脚本,只是为了不邪恶。)说了这么多,你可能想要检查它们所有这些支持 local
的 sh
实现的手册页,因为它们在工作方式上可能存在细微的差异。或者只是不要使用 local
:
如果你真的想完全符合POSIX,或者不符合想要解决可能的问题,因此不要使用 local
,那么你有几个选项。 Lars Brinkhoff给出的答案是合理的,你可以将函数包装在一个子shell中。这可能会有其他不良影响。顺便说一句shell语法(per POSIX)允许以下内容:
my_function()
(
#已经在一个子shell中,
#我正在使用(和)函数的主体而不是{和}。
)
虽然也许要避免这种超级可移植性,但一些旧的Bourne shell甚至可能不符合POSIX标准。只是想提及POSIX允许它。
另一个选项是在函数结尾处的 unset
变量但是这不是 将会恢复原来的价值,所以实际上并不是我想要的,它只会阻止变量的函数内值向外泄漏。我猜不是很有用。
最后一个疯狂的想法,我能想到的是自己实现 local
。这个shell有 eval
,然而,这种做法会产生一些疯狂的可能性。下面基本上实现了动态范围设计,我将使用关键字 let
而不是 local 来进一步降低风险即使您必须在最后使用所谓的
unlet
:
#如果你想要的话,你可以添加一些错误检查和什么不是这个。目前,
#的错误用法(例如,将一个带有空格的字符串传递给`let',而不是
#平衡`let'和`unlet'调用一个变量等等)可能会产生
#非常非常混乱的错误信息或破损。这也是非常脏的代码,I
#只是一次写下来。可以清理。
let()
{
dynvar_name = $ 1;
dynvar_value = $ 2;
dynvar_count_var = $ {dynvar_name} _dynvar_count
if [$(eval echo $ dynvar_count_var)]
然后
eval $ dynvar_count_var ='$(($' $ d
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未设置$ dynvar_oldval_var
eval $ dynvar_count_var ='$(($'$ dynvar_count_var' - 1))'
完成
现在您可以:
$ 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委员会的成员,不要把它展示给Bourne先生,也许展示给McCarthy的幽灵让他发笑。您已被警告,并且您没有向我学习。
编辑:
显然我被殴打,发送Freenode上的IRC bot
greybot
(属于 #bash
)命令posixlocal将使它给出一个晦涩难懂的代码,演示如何在POSIX sh中实现局部变量。这是一个有些清理的版本,因为原始文件很难破译: f()
{
if [$ _called_f]
then
x = test1
y = test2
echo $ x $ y
else
_called_f = X x = y =命令eval'{typeset + xxy; } 2> / dev / null; f$ @'
fi
}
$ x = a
$ y = b
$ f
test1 test2
$ echo $ x $ y
ab
因此,可以使用变量 x
和 y
作为和
分支中的局部。可以在 else
分支添加更多变量;请注意,必须在初始列表中添加两次,一次是 variable =
,并且一次作为参数传递给 typeset
。请注意,不需要 unlet
(这是一个透明的实现),并且没有名称修改和过度 eval
完成。所以它看起来总体上是一个更清洁的实现。
编辑2:
出来 typeset
不是由POSIX定义的,Almquist Shell(FreeBSD,NetBSD和Debian)的实现不支持它。所以上述黑客将无法在这些平台上工作。
Is there a POSIX Compliant way to limit the scope of a variable to the function it is declared in? i.e.:
Testing()
{
TEST="testing"
}
Testing
echo "Test is: $TEST"
should print "Test is:". I've read about the declare, local, and typeset keywords, but it doesn't look like they are required POSIX built-ins.
It is normally done with the local
keyword, which is, as you seem to know, not defined by POSIX. Here is an informative discussion about adding 'local' to POSIX.
However, even the most primitive POSIX-compliant shell I know of which is used by some GNU/Linux distributions as the /bin/sh
default, dash
(Debian Almquist Shell), supports it. FreeBSD and NetBSD use ash
, the original Almquist Shell, which also supports it. OpenBSD uses a ksh
implementation for /bin/sh
which also supports it. So unless you're aiming to support non-GNU non-BSD systems like Solaris, or those using standard ksh, etc., you could get away with using local
. (Might want to put some comment right at the start of the script, below the shebang line, noting that it is not strictly a POSIX sh script. Just to be not evil.) Having said all that, you might want to check the respective man-pages of all these sh
implementations that support local
, since they might have subtle differences in how exactly they work. Or just don't use local
:
If you really want to conform fully to POSIX, or don't want to mess with possible issues, and thus not use local
, then you have a couple options. The answer given by Lars Brinkhoff is sound, you can just wrap the function in a sub-shell. This might have other undesired effects though. By the way shell grammar (per POSIX) allows the following:
my_function()
(
# Already in a sub-shell here,
# I'm using ( and ) for the function's body and not { and }.
)
Although maybe avoid that to be super-portable, some old Bourne shells can be even non-POSIX-compliant. Just wanted to mention that POSIX allows it.
Another option would be to unset
variables at the end of your function bodies, but that's not going to restore the old value of course so isn't really what you want I guess, it will merely prevent the variable's in-function value to leak outside. Not very useful I guess.
One last, and crazy, idea I can think of is to implement local
yourself. The shell has eval
, which, however evil, yields way to some insane possibilities. The following basically implements dynamic scoping a la old Lisps, I'll use the keyword let
instead of local
for further cool-points, although you have to use the so-called unlet
at the end:
# 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
Now you can:
$ 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
(By the way unlet
can be given any number of variables at once (as different arguments), for convenience, not showcased above.)
Don't try this at home, don't show it to children, don't show it your co-workers, don't show it to #bash
at Freenode, don't show it to members of the POSIX committee, don't show it to Mr. Bourne, maybe show it to father McCarthy's ghost to give him a laugh. You have been warned, and you didn't learn it from me.
EDIT:
Apparently I've been beaten, sending the IRC bot greybot
on Freenode (belongs to #bash
) the command "posixlocal" will make it give one some obscure code that demonstrates a way to achieve local variables in POSIX sh. Here is a somewhat cleaned up version, because the original was difficult to decipher:
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
}
This transcript demonstrates usage:
$ x=a
$ y=b
$ f
test1 test2
$ echo $x $y
a b
So it lets one use the variables x
and y
as locals in the then
branch of the if form. More variables can be added at the else
branch; note that one must add them twice, once like variable=
in the initial list, and once passed as an argument to typeset
. Note that no unlet
or so is needed (it's a "transparent" implementation), and no name-mangling and excessive eval
is done. So it seems to be a much cleaner implementation overall.
EDIT 2:
Comes out typeset
is not defined by POSIX, and implementations of the Almquist Shell (FreeBSD, NetBSD, Debian) don't support it. So the above hack will not work on those platforms.
这篇关于在shell脚本中将范围变量转换为函数的POSIX兼容方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!