从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 中编程了一段时间,这就不再变得令人困惑)