在shell脚本中将范围变量转换为函数的POSIX兼容方式 [英] POSIX-Compliant Way to Scope Variables to a Function in a Shell Script

查看:140
本文介绍了在shell脚本中将范围变量转换为函数的POSIX兼容方式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有符合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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆