符合POSIX的方法将范围变量扩展到Shell脚本中的函数

Joh*_*ohn 42 variables shell posix scope function

是否有POSIX兼容方法将变量的范围限制为声明的函数?即:

Testing()
{
    TEST="testing"
}

Testing
echo "Test is: $TEST"
Run Code Online (Sandbox Code Playgroud)

应打印"测试是:".我已经阅读过declare,local和typeset关键字,但它们看起来并不像POSIX内置函数.

Tay*_*nUB 56

它通常使用local关键字来完成,正如您所知,这不是由POSIX定义的.以下是关于向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支持所有这些实现的man-pages local,因为它们可能在它们的工作方式上有细微差别.或者只是不要使用local:

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

my_function()
(
  # Already in a sub-shell here,
  # I'm using ( and ) for the function's body and not { and }.
)
Run Code Online (Sandbox Code Playgroud)

虽然可能避免超级便携,但一些旧的Bourne shell甚至可以兼容非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'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
Run Code Online (Sandbox Code Playgroud)

现在你可以:

$ 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
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,unlet可以同时给出任意数量的变量(作为不同的参数),为方便起见,上面没有展示.)

不要在家里试试这个,不要把它展示给孩子,不要把它展示给你的同事,不要把它展示给#bashFreenode,不要把它展示给POSIX委员会成员,不要把它展示给伯恩先生,或许可以向父亲麦卡锡的鬼魂展示给他笑.你被警告了,你没有向我学习.

编辑:

显然我已被殴打,greybot在Freenode上发送IRC机器人(属于#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 "$@"'
    fi
}
Run Code Online (Sandbox Code Playgroud)

此成绩单演示了用法:

$ x=a
$ y=b
$ f
test1 test2
$ echo $x $y
a b
Run Code Online (Sandbox Code Playgroud)

因此,它允许人们使用变量x和if表单ythen分支中的本地人.可以在else分支机构添加更多变量; 请注意,必须将它们添加两次,就像variable=在初始列表中一样,并且一次作为参数传递给typeset.请注意,不需要unlet左右(这是一个"透明"实现),并且没有完成名称修改和过多eval操作.所以它似乎是一个更清洁的整体实施.

编辑2:

出来typeset不是由POSIX定义,Almquist壳牌(FreeBSD中,NetBSD的Debian的)的实现不支持它.所以上面的hack将无法在这些平台上运行.

  • 作为一名前#bash常客,我印象深刻.部分令人震惊,但印象深刻.:) (3认同)
  • 1)决定是否要使用Bash或POSIX sh或究竟是什么.(当然,最好使用正确的编程语言.)2)子shell应该转发它的$?对父母来说很好. (2认同)
  • 我很高兴你在这项可怕的工作中包含了足够可怕的警告。:) (2认同)

Lar*_*off 9

我相信最接近的是将函数体放在子shell中.

试试这个

foo()
{
  ( x=43 ; echo $x )
}

x=42
echo $x
foo
echo $x
Run Code Online (Sandbox Code Playgroud)


Geo*_*xon 9

这实际上内置于 POSIX 函数声明的设计中。

如果您希望在函数中可以访问在父作用域中声明的 变量,但使其在父作用域中的值保持不变,只需:

*使用显式子 shell 声明您的函数,即使用

  • subshell_function() (with parentheses)不是

  • inline_function() { with braces ;}


内联分组与子 shell 分组的行为在整个语言中是一致的。

如果您想“混合和匹配”,请从内联函数开始,然后根据需要嵌套子 shell 函数。它很笨重,但很有效。

  • 但是如果我们想混合操作局部变量和全局变量怎么办?这通常是需要的,也是为什么 local 关键字在 Bash 或其他现代 shell 中很有用。 (3认同)