3.3 Shell 函数(Shell Functions)
Shell 函数是一种将多个命令组织起来,并使用一个名称来代表这些命令,可以在以后执行的命令组。其执行起来就像一个常规命令。当函数作为一个简单命令执行时,关联到该函数的这些命令就会被执行。Shell 函数执行在当前 shell 上下文中;而不会创建新的进程对其进行翻译。
使用下面的语法对函数进行声明:
fname () compound-command [ redirections ]
或
function fname [()] compound-command [ redirections ]
“fname”是函数定义的函数名。保留字“function”是可选的。如果“function”保留字出现,则小括号就是可选的。函数体“compound-command”是复合命令。函数体中的命令通常是用大括号“{”和“}”包围起来的列表,也可以是上面介绍的任意复合命令。但有一种例外:如果使用了保留字“function”,但是没有使用小括号,则必须使用大括号。每当“fname”作为命令名使用时,都会执行“compound-command”。当 shell 运行于 POSIX 模式(参见 6.11《Bash POSIX 模式》)时,“fname”必须是有效的 shell 名字,并且不能和特殊内建命令名(参见 4.4《特殊内建命令》)相同。在默认模式中,函数名可以是任意不以“$”开头的,并且未被引起来的单词。当执行函数时,任何关联到该函数的重定向(参见 3.6《重定向》)都会被执行。定义的函数可使用内建命令“unset”(参见 4.1《Bourne Shell 内建命令》)加选项“-f”进行删除。
一个函数定义的退出状态是零,除非有语法错误或与一个已有的只读函数重名时。在函数执行时,函数的退出状态就是函数体执行的最后一条命令的退出状态。
注意,由于历史原因,在大多数的应用中,函数体和其大括号之间必须使用空白或换行进行分隔。这是因为大括号是保留字,并且只有当其与命令列表使用空格或其它 shell 元字符分隔开来才会被识别出来。同样的,当使用大括号时,命令列表必须使用分号“;”、与符号“&”或换行(newline)作为结束符。
当执行一个函数时,函数的参数(arguments)就在执行期间变为位置参数(参见 3.4.1《位置参数》)。特殊参数井号“#”扩展为位置参数的个数,用于反映其变化。特殊参数“0”是没有变化的。在函数执行期间,变量“FUNCNAME”的第一个元素设置为函数名。
在函数和其调用方之间的 shell 执行环境的所有其它方面都是相同的。但有以下例外:“DEBUG”和“RETURN”捕获器是不继承的,除非使用“declare”命令设定了“trace”属性或使用“set”命令开启了“-o functrace”选项(这会引起所有函数继承“DEBUG”和“RETURN”捕获器);不会继承“ERR”捕获器,除非开启了“-o errtrace”选项。关于内建命令“trap”的详细说明请参见 4.1《Bourne Shell 内建命令》。
如果将变量“FUNCNEST”的值设置为大于零的数字,就是定义了函数的最大内嵌的层数。如果函数调用的嵌套层数超过了这个限制,则会引起整个命令的中止。
如果在函数中执行了内建命令“return”,则该函数执行完成并恢复执行该函数后面的命令。任何关联到“RETURN”捕获器的命令会在恢复执行前执行。当函数执行完成时,位置参数和特殊参数“#”的值都会被还原成该函数执行前的状态。如果在“return”命令后面给出一个数字作为参数,这就是函数的返回状态;否则,函数的返回状态就是其“return”命令之前的最后一条命令的退出状态。
可以使用内建命令“local”来声明函数的本地变量。这种变量仅可见于函数内部并且仅函数内部命令可调用。当函数调用其它函数时,这一点尤其重要。
本地变量遮蔽(shadow)了在上一作用域声明的相同变量名的变量。在实例中,在函数内部声明的本地变量隐藏了相同名称的全局变量:引用(reference)和赋值操作都会对本地变量操作,而不会改变全局变量。当函数返回时,全局变量才会再次变为可见。
在函数内部,shell 使用动态作用域来控制变量的可见性。在动态作用域中,可见变量和其值是一系列函数调用所执行到当前函数的结果。函数可以看到的的变量值是在调用方中的变量值,如果其存在的话,不论其调用方是全局作用或还是某个函数。这个值也是会被声明为遮蔽的本地变量的值所隐藏,并且当函数返回时该值也会被还原。
例如,如果在函数“fun1”中声明一个本地变量“var”,并且“func1”调用了另一个函数“func2”,在“func2”中引用到的“var”变量将解析到“func1”中的“var”变量,该变量遮蔽了所有名为“var”的全局变量。
下面的代码示范了这种行为:
# In func2, var = func1 local
func1()
{
local var='func1 local'
func2
}
func2()
{
echo "In func2, var = $var"
}
var=global
func1
内建命令“unset”也可作用于相同的动态作用域:如果一个本地变量存在于当前作用域中,“unset”命令可以释放它;否则“unset”将释放在任意调用方作用域中的该变量。如果在当前本地作用域中的变量被释放了,其会一直保留直到它在该作用域中被重置(reset)或函数返回。一旦函数返回了,在前一作用域的该变量实例就变为可见的了。如果在前一作用域的变量上进行释放动作,任何与之变量名相同的被遮蔽的变量将变为可见。
可使用内建命令“delcare”或“typeset”(参见 4.2《Bash 内建命令》)加“-f”选项将函数名和其定义列出来。大写“-F”选项可用于只将函数名列出(如果 shell 选项“extdebug”开启,可列出源文件和行号)。可以导出函数,因此可使用内建命令“export”(参见 4.1《Bourne Shell 内建命令》)加选项“-f”将函数定义自动导出到子 shell 中。
函数可以进行递归调用。变量“FUNCNEST”用于限制调用堆栈的深度和限制函数调用的数量。默认情况下,不会限制递归调用的数量。