bash:实验保存和恢复全局变量上下文

bash:实验保存和恢复全局变量上下文

我做了一个小实验,在一行中保存一些上下文。

它完全按照我的要求工作。所以这篇文章的目的是: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>
}

可选择替换typesetlocaldeclare

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 中实现词法作用域? ;-)

相关内容