有没有办法使本地命令可移植地工作以破折号 ksh bash 和 zsh?

ImH*_*ere 5 bash ksh zsh dash shell-script

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

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

$ dash ./script
internal  IFS = 123
descended IFS = 123
external  IFS = abc
Run Code Online (Sandbox Code Playgroud)

这意味着 IFS对函数(和后代)来说仍然是本地的。

但是, ksh 不同,不接受local

$ ksh ./script
./mt2[3]: local: not found [No such file or directory]
Run Code Online (Sandbox Code Playgroud)

将“本地”一词更改为“排版”可使脚本(几乎)在 ksh 中工作,但 dash 无法识别typeset.

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

function testlocal { 
                     typeset IFS
                     IFS=123
                     echo "internal IFS = $IFS"
                     testdescend
                   }
Run Code Online (Sandbox Code Playgroud)

这进一步使破折号的便携性复杂化。

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

Sté*_*las 5

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

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

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

ksh93typeset仅执行静态作用域。POSIX 反对指定 ksh88 的typeset/ local,因为它是动态作用域(尽管大多数语言(如 C)执行静态作用域,但动态作用域在 shell 中更自然地出现,因为它是通过子 shell 或环境实现的效果),这解释了为什么 ksh93 被重写以进行静态应对。

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

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

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

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

给出:

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

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

然后将您的函数声明为:

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

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

这使

internal IFS = 123
descended IFS = 123
external IFS = abc
Run Code Online (Sandbox Code Playgroud)

在所有 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"
Run Code Online (Sandbox Code Playgroud)

现在,如果您要使用 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"
}
Run Code Online (Sandbox Code Playgroud)

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

进一步阅读:

  • @Isaac,看来你想玩文字游戏,所以我不会与你争论。我会将答案留在那里,以防对其他人有用。 (3认同)