我做了一个小实验,在一行中保存一些上下文。
它完全按照我的要求工作。所以这篇文章的目的是:1. 与社区分享。2. 改进它,或者找到一个完整的其他解决方案,因为它很丑陋而且很神秘。
情况如下:有一个全局变量,我希望在函数中本地能够更改它并在最后恢复它:
x()
{
typeset loc=$glob
glob=<val>
(...)
glob=$loc
}
这对我来说太冗长了,并且要求输入错误。
所以我尝试了这个(代码可供使用):
#!/bin/bash
glob=1
indent=0
# appliance
trace()
{
printf "%$((${indent}*3))s %s\n" "" "$1"
}
# the context stuff (creation and destruction)
# in variable, because with a function, I'd need to create a $(sub shell) and it wouldn't work
new_glob='trap restore_context RETURN;typeset loc=$glob;glob'
restore_context()
{
res=$?
glob=$loc
trap - RETURN
}
# common test stuff, to isolate the traces
test_call()
{
typeset res
trace "in ${FUNCNAME[1]}, before $1,glob=$glob"
(( indent++ ))
eval $1
res=$?
(( indent-- ))
trace "in ${FUNCNAME[1]}, res of $1=$res"
trace "in ${FUNCNAME[1]}, after $1,glob=$glob"
return $res
}
# Russian dolls function
f()
{
eval "$new_glob=6"
test_call g
return 16
}
g()
{
eval "$new_glob=7"
test_call h
return 17
}
h()
{
eval "$new_glob=8"
trace "in h, glob=$glob"
i
return 18
}
i()
{
trace "in i, glob=$glob"
}
# main
test_call f
这里脚本调用 f,f 调用 g,g 调用 h。每个函数都会更改全局变量,然后恢复它。
输出:
# ./test_rtrap
in main, before f,glob=1
in f, before g,glob=6
in g, before h,glob=7
in h, glob=8
in i, glob=8
in g, res of h=18
in g, after h,glob=7
in f, res of g=17
in f, after g,glob=6
in main, res of f=16
in main, after f,glob=1
这里重要的一点是这个函数我()打印 8,而不是 1,就像使用局部变量那样。
为了达到这个结果,我在 RETURN 上使用了陷阱函数。
现在上面的函数 x 就变成了:
x()
{
eval "$new_glob=6"
(...)
}
遗憾的是我不得不使用 eval 和包含(部分)代码的变量。这不自然,相当神秘。但我需要它,因为在那里使用函数需要一个子 shell,并且存在相关的变量上下文问题。
所以,虽然不完美,不是很漂亮,但不那么冗长,但它确实有效。
有没有比丑陋的 eval "$new_glob=6" 更好的方法来执行此操作
答案1
远远超出了顶部!至少对于所示的示例来说是这样。
替换eval "$new_glob=6"
为local glob=6
并删除 new_glob 变量。
换句话说,而不是写作
x()
{
typeset loc=$glob
glob=<val>
(...)
glob=$loc
}
写吧
x()
{
typeset glob
glob=<val>
}
可选择替换typeset
为local
或declare
。
bash 是一个动态范围语言。目前,在链接的文章中描述了一种实施策略
更简单的实现是用简单的全局变量来表示动态变量。本地绑定是通过将原始值保存在堆栈上对程序不可见的匿名位置来执行的。当该绑定范围终止时,将从该位置恢复原始值。事实上,动态作用域就是以这种方式起源的。 Lisp 的早期实现使用了这种明显的策略来实现局部变量,并且这种做法在一些仍在使用的方言中得以保留,例如 GNU Emacs Lisp。
它几乎描述了提供的 bash 代码。
答案2
好的(感谢伊卡洛斯),所以恕我直言,bash 在这里真的很奇怪。我基本上重新实现了它的行为。
bash 中的局部变量不是局部变量!它们只是在本地更改/设置全局/环境变量的值,并在退出“本地定义”的函数时恢复它们的值。这称为动态作用域。
bash 中的 Local 意味着“全局变量的本地值”,这对我来说很奇怪。例如,ksh93 的行为不同,局部变量实际上是局部变量(即隐藏全局变量)。这称为词法/动态作用域,到目前为止,大多数语言都使用它。
为了说明这一点,我编写了另一个测试程序:
if (( $# == 0 )); then
# run this script with specified shells
bash -c "$0 -c"
ksh93 -c "$0 -c"
exit 0
fi
# which shell am I
readlink /proc/$$/exe
# declarations
local="Dynamic scoping" # i.e. func i view "local" value
global="Lexical/Static scoping" # i.e. func i views "global" value
glob="$global"
# non POSIX declarations
function f { typeset glob="$local"; i; }
function i { echo " $glob"; }
# call
echo " non POSIX ( function f )"
f
# POSIX declations
pf() { typeset glob="$local"; i; }
pi() { echo " $glob"; }
# call
echo " POSIXi ( f() )"
pf
从语法上来说,这些指令/脚本可以在 bash 和 ksh93 上运行。
函数f定义一个局部变量,并调用i。在词法作用域情况下,函数 i 可以访问全局变量,因为它是在函数 f 作用域之外进行词法定义的。在动态作用域中,不创建局部变量,仅创建全局变量,函数 f 仅在其自己的执行生命周期内设置其值。当函数i被调用时,函数f仍然“活着”,因此值全局变量value仍然具有赋值给函数f的值。仅当函数 f 返回时,shell 才会恢复该函数更改的值。
这是它的输出:
/u-blox/gallery/ubx/det/re6_64/8.0/bin/bash
non POSIX ( function f )
Dynamic scoping
POSIXi ( f() )
Dynamic scoping
/bin/ksh93
non POSIX ( function f )
Lexical/Static scoping
POSIXi ( f() )
Dynamic scoping
现在的问题是:如何在 bash 中实现词法作用域? ;-)