在 Shell 脚本中将变量作用域到函数的 POSIX 兼容方式 [英] POSIX-Compliant Way to Scope Variables to a Function in a Shell Script

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

问题描述

是否有符合 POSIX 标准的方法来将变量的范围限制在声明它的函数中?即:

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"

应该打印测试是:".我已经阅读了声明、本地和排版关键字,但看起来它们不是 POSIX 内置函数所必需的.

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.

推荐答案

通常使用 local 关键字来完成,正如您所知,POSIX 并未定义该关键字.这是一个内容丰富的关于向 POSIX 添加本地"的讨论.

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.

然而,即使是我所知道的最原始的 POSIX 兼容 shell 也被一些 GNU/Linux 发行版用作 /bin/sh 默认值,dash(DebianAlmquist Shell),支持它.FreeBSD 和 NetBSD 使用 ash,原始的 Almquist Shell,它也支持它.OpenBSD 为 /bin/sh 使用了一个 ksh 实现,它也支持它.因此,除非您的目标是支持非 GNU 非 BSD 系统,如 Solaris,或那些使用标准 ksh 等的系统,否则您可以使用 local.(可能想在脚本的开头加上一些注释,在 shebang 行下方,注意它不是严格的 POSIX sh 脚本.只是为了不作恶.)说了这么多,你可能想检查各自的支持 local 的所有这些 sh 实现的手册页,因为它们的工作方式可能存在细微差别.或者干脆不要使用 local:

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:

如果您真的想完全符合 POSIX,或者不想弄乱可能的问题,因此不使用 local,那么您有几个选择.Lars Brinkhoff 给出的答案是合理的,您可以将函数包装在子外壳中.不过,这可能会产生其他不良影响.顺便说一句,shell 语法(根据 POSIX)允许以下内容:

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 }.
)

虽然为了超级便携可能会避免这种情况,但一些旧的 Bourne shell 甚至可能不符合 POSIX 标准.只是想提一下 POSIX 允许它.

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.

另一种选择是在函数体的末尾unset 变量,但这不是当然会恢复旧值,所以不是你真正想要的我想,它只会防止变量的函数内值泄漏到外面.我猜不是很有用.

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.

我能想到的最后一个疯狂的想法是自己实现 local.shell 有 eval,无论多么邪恶,它都会产生一些疯狂的可能性.下面基本上实现了旧 Lisps 的动态范围,我将使用关键字 let 而不是 local 来获得更多的酷点,尽管您必须使用所谓的unlet 结尾:

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

现在您可以:

$ 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 可以一次被赋予任意数量的变量(作为不同的参数),为方便起见,上面没有展示.)

(By the way unlet can be given any number of variables at once (as different arguments), for convenience, not showcased above.)

不要在家里尝试,不要给孩子看,不要给你的同事看,不要给 Freenode 的 #bash 看,不要给 POSIX 委员会的成员看,不要给伯恩先生看,也许给麦卡锡神父的鬼魂看,让他开怀大笑.你被警告过,你没有从我这里学到.

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.

显然我被打败了,在 Freenode(属于 #bash)上发送 IRC bot greybot 命令posixlocal"会让它给出一些晦涩的代码这演示了一种在 POSIX sh 中实现局部变量的方法.这是一个稍微清理过的版本,因为原始版本难以破译:

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
}

此成绩单演示了用法:

$ x=a
$ y=b
$ f
test1 test2
$ echo $x $y
a b

所以它允许使用变量 xy 作为 if 形式的 then 分支中的局部变量.else 分支可以添加更多变量;请注意,必须将它们添加两次,一次像初始列表中的 variable=,一次作为参数传递给 typeset.请注意,不需要 unlet 左右(这是一个透明"的实现),并且没有进行名称修改和过多的 eval.因此,总体而言,这似乎是一个更清晰的实现.

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.

编辑 2:

出来 typeset 不是由 POSIX 定义的,并且 Almquist Shell(FreeBSD、NetBSD、Debian)的实现不支持它.所以上述 hack 在这些平台上不起作用.

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天全站免登陆