Bash - Shell 变量中的函数

Jak*_*ake 7 bash

我正在阅读这篇关于在 bash shell 变量中使用函数的文章。我观察到要使用 shell 函数,必须导出它们并在子 shell 中执行它们,如下所示:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function
Run Code Online (Sandbox Code Playgroud)

我想问一下是否可以在 bash shell 变量中定义函数,以便它们可以在当前 shell 中执行而不使用 export & running new shell?

cuo*_*glm 8

不,你不能。

如果你想在当前 shell 中使用一个函数,只需定义它然后稍后使用它:

$ foo() { echo "Inside function"; }
$ foo
Inside function
Run Code Online (Sandbox Code Playgroud)

Shellshock之前,您可以将函数存储在一个变量中,导出变量并在子 shell 中使用函数,因为bash支持导出函数,bash函数定义放在环境变量中,例如:

foo=() {
  echo "Inside function"
}
Run Code Online (Sandbox Code Playgroud)

然后通过=用空格替换来解释函数。无意将函数放在变量中并引用变量将执行函数。

现在,在Stéphane Chazelas发现错误后,该功能被删除,不再存储在普通环境变量中的函数定义。现在导出的函数将通过附加前缀BASH_FUNC_和后缀进行编码,%%以避免与环境变量发生冲突。bash解释器现在可以确定它们是否是 shell 函数,而不管变量的内容如何。您还需要定义该函数并将其显式导出以在子 shell 中使用它:

$ foo() { echo "Inside function"; }
$ export -f foo
$ bash -c foo
Inside function
Run Code Online (Sandbox Code Playgroud)

无论如何,如果您的示例适用于您当前的 . bash,那么您使用的是易受攻击的版本。

  • BTW:这个特性总是与 POSIX 标准相冲突。 (2认同)

mik*_*erv 6

foo='foo(){ echo "Inside Function"; }'
bash -c "$foo; foo"
Run Code Online (Sandbox Code Playgroud)
Inside Function
Run Code Online (Sandbox Code Playgroud)

毕竟,函数只是一个存储在 shell 内存中的命名的、预先解析的、静态的命令字符串。因此,唯一真正的方法是将字符串作为代码进行评估,这正是每次调用函数时 shell 所做的。

以前从未如此不同,现在仍然如此。开发人员为此设置了方便的方法,他们试图尽可能地保护可能的安全漏洞,但事实是,事情变得越方便,你就越有可能对它了解得比你应该知道的少。

在将数据从父 shell 导入到子 shell 时,有许多选项可供您使用。逐渐熟悉它们,变得足够自信,可以确定它们的潜在用途和潜在弱点,并谨慎但有效地使用它们。


我想我可以详细说明其中的一两个。将静态命令从一个 shell 传递到另一个 shell (无论是在子 shell 命令链上还是下)的最佳方式可能是alias. alias在这种情况下很有用,因为它是POSIX 指定的,可以安全地报告其定义的命令:

显示别名的格式(当未指定操作数或仅指定名称操作数时)应为:

  • "%s=%s\n", name, value

的字符串应当用适当的引用,使得它适合于重新输入到外壳被写入。见壳引用的说明引用

例如:

alias foo="foo(){ echo 'Inside Function'; }"
alias foo
Run Code Online (Sandbox Code Playgroud)
foo='foo(){ echo '\''Inside Function'\''; }'
Run Code Online (Sandbox Code Playgroud)

看看它对引号做了什么,那里?确切的输出取决于 shell,但一般来说,您可以依赖 shellaliaseval安全的方式报告其定义,以便重新输入到同一shell。不过,在某些情况下,混合和匹配源/目标 shell 可能是不确定的。

为了证明谨慎行事的原因:

ksh -c 'alias foo="
    foo(){
        echo \"Inside Function\"
    }"
    alias foo'
Run Code Online (Sandbox Code Playgroud)
foo=$'\nfoo(){\n\techo "Inside Function"\n}'
Run Code Online (Sandbox Code Playgroud)

另请注意,alias命名空间代表三个独立命名空间之一,您通常可以期望在几乎任何现代 shell 中找到完全充实的命名空间。通常,shell 为您提供这三个命名空间:

  • 变量:列表name=value之外

    • 在某些情况下export,这些也可以用一些内置函数定义,例如, set, readonly
  • 功能:name() compound_command <>any_redirects指挥位置

    • 指定 shell扩展或解释它在定义时可能在命令中找到的函数定义的任何部分。
      • 但是,此禁令不包括别名,因为它们在 shell 的命令读取的解析过程中被扩展,因此alias如果alias在正确的上下文中找到值,则在找到时将其扩展到函数定义命令中。
    • 如前所述,shell 将定义命令的解析值作为静态字符串保存在其内存中,并且仅当函数名称在调用后解析后找到时,它才会执行在字符串中找到的所有扩展和重定向。
  • 别名:alias name=value指挥位置

    • 与函数一样,alias定义名称在稍后在命令位置找到时被解释。
    • 但是,与函数不同的是,因为它们的定义是alias命令的参数,所以在定义时也会解释其中的有效 shell 扩展。所以alias定义总是被解释两次。
    • 同样与函数不同的是,alias定义在定义时根本不会被解析,而是 shell 的解析器在找到它们时将它们的值预先解析为命令字符串。

您可能已经注意到我认为是上述 analias或 a 函数之间的主要区别,即每个函数的 shell 扩展点。实际上,这些差异可以使它们的组合非常有用。

alias foo='
    foo(){
        printf "Inside Function: %s\n" "$@"
        unset -f foo
    };  foo "$@" '
Run Code Online (Sandbox Code Playgroud)

这是一个别名,它将定义一个函数,该函数在调用时会自行取消设置,因此它会通过另一个名称空间的应用程序影响一个名称空间。然后调用该函数。这个例子很幼稚;它在常规上下文中不是很有用,但以下示例可能会展示它在其他上下文中可能提供的其他一些有限的用途:

sh -c "
    alias $(alias foo)
    foo \'
" -- \; \' \" \\
Run Code Online (Sandbox Code Playgroud)
Inside Function: ;
Inside Function: '
Inside Function: "
Inside Function: \
Inside Function: '
Run Code Online (Sandbox Code Playgroud)

您应该始终alias在输入行上调用一个名称,而不是在其上找到定义的行 - 同样,这与命令的解析顺序有关。结合使用时,别名和函数可以一起使用,以可移植、安全和有效地将信息和参数数组移入和移出子壳上下文。


几乎不相关,但这是另一个有趣的:

alias mlinesh='"${SHELL:-sh}" <<"" '
Run Code Online (Sandbox Code Playgroud)

在交互式提示下运行它,您在与您调用它的行相同的执行行上传递给它的任何参数都将被解释为子 shell 的参数。但是,此后的所有行直到第一个出现的空行都被当前 shell 读入,以作为 stdin 传递给它准备调用的子 shell,并且根本不会以任何方式解释这些行。

$ mlinesh -c '. /dev/fd/0; foo'
> foo(){ echo 'Inside Function'; } 
> 
Run Code Online (Sandbox Code Playgroud)
Inside Function
Run Code Online (Sandbox Code Playgroud)

...只是...

$ mlinesh
> foo(){ echo 'Inside Function'; }
> foo
> 
Run Code Online (Sandbox Code Playgroud)

……更容易……