同名的 shell 函数和变量

同名的 shell 函数和变量

bash手册:

请注意,具有相同名称的 shell 函数和变量可能会导致环境中传递给 shell 子级的多个同名条目。如果这可能会导致问题,请务必小心。

bash如何区分“同名的shell函数和变量”?

$  func () { return 3; }; func=4; declare -p func; declare -f func;
declare -- func="4"
func () 
{ 
  return 3
}

“环境中的多个同名条目传递给 shell 的子级”何时发生?

遇到什么问题应该“注意”什么?

答案1

总体情况:独立的命名空间

一般来说,shell 会区分变量和函数,因为它们在不同的上下文中使用。简而言之,如果名称出现在 a 之后$,或者作为内置函数(例如export(without -f) 和unset(without -f) 的参数),则该名称是变量名。如果名称作为命令(别名扩展后)或作为 、 等的参数出现,则该名称是函数export -f名称unset -f

变量可以导出到环境中。环境变量的名称与 shell 变量相同(并且值也相同)。

对于较旧的 bash:由于函数导出而造成混乱

与大多数其他 shell 不同,Bash 还可以将函数导出到环境中。由于环境中没有类型指示,因此除了分析环境变量的名称或值之外,无法识别环境中的条目是否是函数。

旧版本的 bash 在环境中存储函数,使用函数的名称作为名称,使用看起来像函数定义的东西作为函数的值。例如:

bash-4.1$ foobar () { echo foobar; }
bash-4.1$ export -f foobar
bash-4.1$ env |grep -A1 foobar
foobar=() {  echo foobar
}
bash-4.1$ 

请注意,无法区分代码为 的函数{ echo foobar; }和值为() { echo foobar␤}(其中是换行符)的变量。事实证明这是一个糟糕的设计决策。

有时,shell 脚本会使用环境变量来调用,这些环境变量的值受到潜在敌对实体的控制。例如,CGI 脚本。 Bash 的函数导出/导入功能允许以这种方式注入函数。例如执行脚本

#!/bin/bash
ls

只要环境不包含具有特定名称(例如PATH)的变量,来自远程请求的请求就是安全的。但是,如果请求可以将环境变量设置ls为,() { cat /etc/passwd; }那么 bash 会很乐意执行,cat /etc/passwd因为这是函数的主体ls

使用较新的 bash:混乱程度大大减轻

该安全漏洞是由斯蒂芬·查泽拉斯作为其中的一个方面炮弹休克虫。在 bash 的后 Shellshock 版本中,导出的函数由其标识姓名而不是通过它们的内容。

bash-4.3$ foobar () { echo foobar; }
bash-4.3$ export -f foobar
bash-4.3$ env |grep -A1 foobar
BASH_FUNC_foobar%%=() {  echo foobar
}

现在不存在安全问题,因为类似的名称BASH_FUNC_foobar%%不常用作命令名称,可以通过允许传递环境变量的接口过滤掉。从技术上讲,在环境变量的名称中包含一个%字符是可能的(这就是现代 bash 的导出函数能够工作的原因),但通常人们不会这样做,因为 shell 不接受%变量名称。

bash 手册中的这句话指的是旧的(Shellshock 之前)行为。应该更新或删除它。对于现代 bash 版本,如果您假设环境变量的名称不以%%.

答案2

Emacs Lisp 中也发生类似的情况。它有两个命名空间,一个用于函数,一个用于变量。如果您在函数上下文 ( (var)) 中取消引用一个符号,它将调用该函数,如果您在变量上下文中取消引用它(var即没有括号),它将为您提供变量。例如:

(defun myvar (myvar)
  "adds 3 to MYVAR"
  (+ 3 myvar))
(setq myvar 7)
(message (myvar myvar))

将执行功能 myvar参数7是变量的取消引用myvar

如果您不习惯的话,这可能会变得非常混乱。

查看您的问题并进行测试后巴什我很惊讶它呈现出相同的行为。将上面的 ELisp 翻译成 bash:

[grochmal@phoenix ~]$ myvar () { echo $(($1+3)); }
[grochmal@phoenix ~]$ myvar=7
[grochmal@phoenix ~]$ myvar $myvar
10

在这方面,Bash 比 ELisp 更容易混淆,因为您需要 来$标记变量。尽管如此,这可能看起来像是一个declare包含两个内容的名称。看:

[grochmal@phoenix ~]$ declare -p myvar
declare -x myvar="7"
[grochmal@phoenix ~]$ declare -f myvar
myvar () 
{ 
    echo $(($1+3))
}

(PS 一旦你习惯了两个命名空间的存在,例如你在 ELisp 中编程了一段时间,这就不再变得令人困惑)

相关内容