有没有办法让本地命令可移植到 dash ksh bash 和 zsh?

有没有办法让本地命令可移植到 dash ksh bash 和 zsh?

在 dash(以及 bash zsh 和其他一些 shell)中,该命令的local作用是将变量范围限制为该函数(在某些情况下还包括子函数)。这使得可以将变量限制为该函数内部结构仅(以及在某些情况下的后代函数调用)。

例如:

testlocal(){ 
                local IFS
                IFS=123
                echo "internal  IFS = $IFS"
                testdescend
}

testdescend(){
                echo "descended IFS = $IFS"
}

IFS=abc
testlocal
echo "external  IFS = $IFS"

在 dash(以及 bash 和 zsh)中执行时产生以下输出:

$ dash ./script
internal  IFS = 123
descended IFS = 123
external  IFS = abc

这意味着 IFS 仍然存在当地的到函数(和后代)。

然而,ksh 不同,不接受local

$ ksh ./script
./mt2[3]: local: not found [No such file or directory]

将“local”一词更改为“typeset”使脚本(几乎)可以在 ksh 中运行,但 dash 无法识别typeset

function并且,为了使 ksh 中的范围动态(下降到被调用的函数),必须使用该词来定义函数:

function testlocal { 
                     typeset IFS
                     IFS=123
                     echo "internal IFS = $IFS"
                     testdescend
                   }

这使得 Dash 的可移植性进一步复杂化。

问题是:如何使原始脚本也在 ksh 中工作?是否可以避免测试哪个 shell 正在运行脚本?

答案1

local请注意,ksh88 及其所有克隆都执行动态作用域,并且至少从 1990 年的 ksh88 和 1994 年的 ksh88 pdksh(以及 1989 年bash(现在)实现了许多 ksh88 API)开始支持它。

ksh所指的是ksh93David Korn 从头开始​​的一个较新的实现,其 API 略有不同且不兼容。

最好将该 shell 称为API,ksh93而不是仅仅kshkshksh88十年来的 API(除了 ksh93 实现之外的所有实现)。ksh93直到 2000 年之后几年,其代码作为开源发布,才得到广泛使用。

ksh93 的typeset唯一作用静止的范围界定。 POSIX 反对指定 ksh88 的typeset/ ,local因为它是动态作用域(尽管如此,尽管大多数语言(如 C)都这样做)静止的范围界定,动态的范围界定在 shell 中更自然,因为它是您通过子 shell 或环境获得的效果),这解释了为什么 ksh93 被重写为静态应对。

后来,大多数其他 shell 都实现了 ksh88 的作用域,因此 ksh93 现在是例外。现在讽刺的是,POSIX 唯一合理的选择是指定动态范围。虽然 David Korn 最初拒绝在 ksh93 中实现动态作用域,但他曾表示可以使用localPOSIX 邮件列表上的关键字/builtin 来考虑它,但在这一切完全实现之前他已经退休了。

AT&T 的 ksh93v- beta 和最终版本可以使用实验性“bash”模式(实际上默认启用)进行编译,该模式在作为 调用时执行动态作用域(以函数形式,包括 withlocaltypeset)。ksh93bashbashksh2020 中默认禁用该模式尽管即使未编译 bash 模式,local/别名declare也会保留typeset(尽管仍然具有静态范围)。

现在,如果我们将 beta 版本及其 bash 模式放在一边,ksh93仅有的执行静态作用域,并且仅在使用 ksh 样式语法 ( ) 声明的函数中进行function name { code; }。您会感到困惑,因为使用 Bourne 风格语法 ( ) 声明的函数f() command不执行作用域根本不。这与使用 调用的源文件或 ksh 函数相同. name [args]。在这些中,typeset不会在函数的作用域中声明新变量(该函数没有作用域),它只是更新当前作用域中变量的类型,该变量的类型将是全局作用域或 ksh- 的作用域style 函数,如果 Bourne 风格(最终)是从 ksh 风格函数中调用的。

Bourne 风格函数的代码在调用时就像嵌入/复制粘贴/来源一样运行。

var=global
function ksh_function {
  typeset var=private
  echo "ksh1: $var"
  bourne_function
  echo "ksh2: $var"
  other_ksh_function other
  echo "ksh3: $var"
  . other_ksh_function other_invoked_with_dot
  echo "ksh4: $var"
}
bourne_function() {
  typeset var=set-from-bourne-function
}
function other_ksh_function {
  echo "other: $var"
  var=$1
}
ksh_function
echo "global: $var"

给出:

ksh1: private
ksh2: set-from-bourne-function
other: global
ksh3: set-from-bourne-function
other: set-from-bourne-function
ksh4: other_invoked_with_dot
global: other

在 ksh93 中,除了使用子 shell 或自己实现变量堆栈之外,不可能具有动态作用域,如locvar概念证明或者你出口要传递给每个命令的变量(包括使用 ksh 样式函数声明的函数,包括通过环境的外部命令)。

在您的特定情况下,只有testlocal函数(而不是testdescend)需要局部作用域,您可以使用所述的shdef+方法kshdef那里或者做类似的事情:

case $KSH_VERSION in
  (*" 93"*)
    fn_with_local_scope() {
      alias local=typeset
      eval "function $1 {
        $(cat)
      }"
    }
  ;;
  (*)
    fn_with_local_scope() {
      eval "$1() {
        $(cat)
      }"
    }
  ;;
esac

然后将您的函数声明为:

fn_with_local_scope testlocal << '}'
  local IFS
  IFS=123
  echo "internal IFS = $IFS"
  testdescend
}

testdescend(){
  echo "descended IFS = $IFS"
}

IFS=abc
testlocal
echo "external IFS = $IFS"

(并且仅local在使用 声明的函数中使用fn_with_local_scope)。

这使

internal IFS = 123
descended IFS = 123
external IFS = abc

在所有 shell 中(请注意,您需要最新版本yash(2.48 或更高版本)才能支持local)。

或者,如果您同意导出局部变量(仅限 ksh93):

case $KSH_VERSION in
  (*" 93"*)
    fn() {
      alias local='typeset -x'
      eval "function $1 {
        $(cat)
      }"
    }
  ;;
  (*)
    fn() {
      eval "$1() {
        $(cat)
      }"
    }
  ;;
esac

fn testlocal << '}'
  local IFS
  IFS=123
  echo "internal IFS = $IFS"
  testdescend
}

fn testdescend << '}'
  echo "descended IFS = $IFS"
}

IFS=abc
testlocal
echo "external IFS = $IFS"

现在,如果您要使用 C 或 ksh93 等具有静态作用域的语言执行类似的操作,您将执行以下操作:

function testlocal {
  typeset IFS
  IFS=123
  echo "internal IFS = $IFS"
  testdescend "$IFS"
}

function testdescend {
  typeset IFS="$1" # explicitly get the value $IFS from the caller
  echo "descended IFS = $IFS"
}

在我看来,这是一个更好的设计,并且该代码在执行动态作用域的 shell 中也可以正常工作(您仍然需要解决不同的函数定义语法)。

进一步阅读:

答案2

我更喜欢 bash(我认为更好的数组语法),但有时会遇到 ksh 脚本,因此我尝试在方便时执行这些操作:

始终使用function func_name { body; }语法。

基于上述,要local在第一个函数之前使用关键字:,请使用:

# teach ksh 93 about local
case "$KSH_VERSION" in *' 93'*) alias local='typeset -x' ;; esac

要获取函数内的函数名称:使用:

local MYNAME="${FUNCNAME[0]:-$0}"

相关内容