Bash - Shell 变量中的函数

Bash - Shell 变量中的函数

我正在读这个邮政关于在 bash shell 变量中使用函数。我发现要使用 shell 函数,必须导出它们并在子 shell 中执行它们,如下所示:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

我想问是否可以在 bash shell 变量中定义函数,以便可以在当前 shell 中执行它们,而无需使用导出并运行新 shell ?

答案1

不,你不能。

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

$ foo() { echo "Inside function"; }
$ foo
Inside function

炮弹休克今天,您可以将函数存储在变量中,导出变量并在子shell中使用函数,因为bash支持导出函数,bash将函数定义放入环境变量中喜欢:

foo=() {
  echo "Inside function"
}

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

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

$ foo() { echo "Inside function"; }
$ export -f foo
$ bash -c foo
Inside function

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

答案2

foo='foo(){ echo "Inside Function"; }'
bash -c "$foo; foo"

Inside Function

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

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

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


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

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

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

价值字符串应使用适当的引号编写,以便适合重新输入到 shell。参见shell引用的描述引用

例如:

alias foo="foo(){ echo 'Inside Function'; }"
alias foo

foo='foo(){ echo '\''Inside Function'\''; }'

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

为了证明采取这种谨慎态度的原因:

ksh -c 'alias foo="
    foo(){
        echo \"Inside Function\"
    }"
    alias foo'

foo=$'\nfoo(){\n\techo "Inside Function"\n}'

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

  • 变量:name=value在外面列表

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

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

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

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

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

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

sh -c "
    alias $(alias foo)
    foo \'
" -- \; \' \" \\

Inside Function: ;
Inside Function: '
Inside Function: "
Inside Function: \
Inside Function: '

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


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

alias mlinesh='"${SHELL:-sh}" <<"" '

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

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

Inside Function

...只是...

$ mlinesh
> foo(){ echo 'Inside Function'; }
> foo
> 

...更容易...

相关内容