许多 Bash 脚本都用严格的命令式风格编写:先执行一条命令,再执行另一条命令,依此类推。 有时你需要把一系列在概念上完成同一件事的命令组合在一起。 这时就要用到函数了。
像这样定义一个函数:
my_function () {
COMMANDS
}
那对空括号只是表示你在定义一个函数。 括号里面不写任何东西。
函数一旦定义好,就和任何其他命令(内置的或不是内置的)一样。
和任何命令一样,你可以向函数提供实参。
在函数内部,你通过位置参数$1、$2 等来访问这些实参。
(回想一下,我们在 变量 概念里学过位置参数。)
特殊参数$0在函数内部不会改变,它仍然是正在执行的脚本的名字。
当前正在执行的函数可以用$FUNCNAME变量访问自己的名字。
参见手册中的 3.4.2 特殊参数。
你可以在函数内部定义变量。
如果你用local命令声明变量,变量的作用域就只限于当前函数(以及由它调用的任何函数)。
否则,变量就处于全局作用域中。
局部变量可以和全局变量同名。 在这种情况下,局部变量会“遮蔽”全局变量。 例如,在函数里声明的局部变量会隐藏同名的全局变量:引用和赋值都指向这个局部变量,全局变量保持不变。 函数返回后,全局变量又重新可见。
x=5
myfunc () {
local x=100
echo "in my function, $x == 100"
}
echo "in the global scope, $x == 5"
myfunc
echo "back in the global scope, $x == 5"
输出结果为
in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5
在函数内部,你可以访问调用者作用域中的变量。 也就是说,你既可以使用全局变量,也可以使用在调用者中声明的局部变量(或在调用调用者的某个函数中声明的局部变量)。
严格来说,“全局”并不是准确的说法。 在函数中展开某个变量时,Bash 会沿着调用栈一路向上查找,直到全局作用域,寻找声明了该变量名的函数。
下面这个例子改编自手册的 Shell 函数 一节:
func1() {
local var='func1 local'
func2
}
func2() {
echo "In func2, var = $var"
}
var=global
func1
func2
输出为:
In func2, var = func1 local
In func2, var = global
同样,给变量赋值时,赋值会发生在声明该变量的作用域里。 这种“远距离作用”会让代码难以理解,因为变量究竟在哪儿被赋值并不总是那么明显。
可以用 FUNCNAME数组变量 来查看调用栈。
函数和任何命令一样,都有一个退出状态。 默认情况下,函数的状态就是最后执行的那条命令的退出状态。
你可以用return命令以特定的退出状态从函数中返回。
check_password () {
if [[ $1 == "secret" ]]; then
return 0
else
return 1
fi
}
read -sp "Enter your password: " pass
if check_password "$pass"; then
echo "Correct!"
else
echo "Wrong password."
fi
不带参数使用return时,返回的是最后执行的那条命令的状态。
注意,check_password函数可以简化成:
check_password () { [[ $1 == "secret" ]]; }
[[...]]条件结构也有退出状态:0表示“真”,1表示“假”。{...}分组结构在结尾的右大括号之前必须有换行或分号。函数的返回状态只是一个数字。 函数怎样才能产生输出呢?
你的函数可以打印到标准输出。 用熟悉的命令替换把它捕获下来:
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }
die=$( d6 )
echo "You rolled a $die."
即使你正在捕获输出,函数的退出状态也一样可以使用。
roll () {
local n=$1
if (( 4 <= n && n <= 20 )); then
echo "$(( 1 + RANDOM % n ))" # exit status is 0
else
return 1
fi
}
read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
echo "You rolled a $die."
else
echo "I can't roll a die with $faces faces."
fi
函数可以递归地调用自身。 默认情况下,递归深度没有限制。
举个例子:
fibonacci() {
local n=$1
if (( n <= 1 )); then
echo "1"
else
local a=$(fibonacci "$(( n - 1 ))")
local b=$(fibonacci "$(( n - 2 ))")
echo "$(( a + b ))"
fi
}
for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
可以用 FUNCNEST变量 控制递归深度。
bash -c '
recur() {
echo $1
recur $(($1 + 1))
}
FUNCNEST=5
recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)