Bash shell 函数

简介

函数是被赋予特定名字的代码块,可以再脚本中任意位置调用,合理地提取函数可以减少脚本代码的重复量,同时也方便去构建代码逻辑结构。在Bash Shell中,函数可以被理解为轻量级的脚本,执行函数类似与运行一个脚本,可以传递参数,也有返回值。

基本使用

创建函数

创建函数有两种方式,确切说是两种创建函数的语法,第一种是:

1
2
3
function name {
commands
}

第二种是:

1
2
3
name() {
commands
}

上面的两种方式是等效的。

调用和返回

创建函数后,调用函数和运行命令类似:

1
name [arg]

上述为调用name函数,arg是传递给函数的参数。调用函数之后,将会以此执行commands中的命令,默认情况下,函数将以最后一个命令的退出状态码退出,该码保存在$?中,需要注意的是$?保存的是最新执行命令的退出状态码,因此如果需要该码一定要及时地读取和处理,以防被后续命令的退出状态码覆盖。

return

利用函数的默认退出状态码,很不方便去获取函数执行完成后的状态,比如中途有个地方执行出错了,我对此一无所知。Bash Shell提供了return命令用来控制函数何时退出以及返回什么状态码(范围是0~255)。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name() {
for item in `ls /home/$USER/*`
do
if [ -f $item ]
then
filename=$(basename $item)
if [[ $filename = "test.sh" ]]
then
return 10
fi
fi
done
return 20
}
name
status=$?
if (( status == 10 ))
then
echo "Found,status=$status, path=$item"
else
echo "Not Found, status=$status"
fi

返回任意内容

上述的return命令虽然可以控制函数的终止和退出状态码,但是就是上面所述,很明显有着很大的限制。除了return外,我们还有另外一种方式来返回数据,也就是利用函数中的输出,在调用函数的时候可以利用一个变量来捕获函数中的输出,从而达到返回的效果。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function name {
for item in `ls /home/$USER/*`
do
if [ -f $item ]
then
filename=$(basename $item)
if [[ $filename = "test.sh" ]]
then
echo "success"
return 10
fi
fi
done
echo "fail"
return 20
}
msg=`name`
status=$?
if (( status == 10 ))
then
echo "$msg,status=$status, path=$item"
else
echo "$msg, status=$status"
fi

上述利用msg变量捕获了name函数的输出,再结合return命令,可以很好的对函数进行控制。需要注意的是,msg会捕获函数中的基本所有输出,除了一些命令如read设置的提示输入字符,所以使用的时候需要留心。

了解了后面讲述的作用域之后,很明显还可以用全局变量来获取函数中的数据当作返回值,但是这样子一定要相当留心。

作用域

和很多变成语言一样,变量通常都有作用域,即变量在规定的作用域才会有意义,作用域定义了变量的可见区域。函数中分有全局变量局部变量,全局变量在脚本中全局可见,而局部变量则只在其定义的函数中可见。

在脚本函数外定义的变量就是全局变量,在任何地方都可以访问到。而在函数内部定义的变量,则是局部变量,只能函数内部可见。例如:

全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
func() {
echo "before set: $global_value"
global_value=$[ $global_value + 1 ]
}
global_value=5
echo "init: $global_value"
func
echo "after set: $global_value"

# 输出为:
# init: 5
# before set: 5
# after set: 6

局部变量

1
2
3
4
5
6
7
8
9
10
func() {
global_value=5
echo "init in function: $global_value"
}
echo "get in global: $global_value"
func

# 输出为:
# get in global:
# init in function: 5

可以看出,函数内部可以访问全局变量,而函数内部的变量外部无法访问。

local命令

local命令可以函数内指定某个变量为局部变量,即便该变量和全局变量同名。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func() {
echo "before set: $global_value"
local global_value=$[ $global_value + 1 ]
echo "after local set: $global_value"
}
global_value=5
echo "init: $global_value"
func
echo "after set: $global_value"

# 输出为:
# init: 5
# before set: 5
# after local set: 6
# after set: 5

函数参数

和一开始讲到那样,函数就像是轻量级的命令,同样的,函数传递参数和命令传递参数是一样的,就是在函数内获取参数,也是同样的获取方法,用一些特殊变量来保存这些参数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func() {
echo "\$0: $0"
echo "\$#: $#"
echo "\$@: $@"
echo "\$*: $*"
for (( i = 1; i < $#; i++ ))
do
echo "\$${i}: ${!i}" # 大括号中不能用$符号,用!代替
done
}
func first second
# 输出为:
# $0: ./shell/test.sh
# $#: 2
# $@: first second
# $*: first second
# $1: first
# $2: second

可以看见,除了$0还是表示脚本名称外,其他的都表示与函数传入参数相关的信息了。如果在函数中使用shift命令,那么将对函数的传入参数进行移位。

导入外部脚本

通常的,对于比较复杂的脚本,我们可以将脚本拆分为多个,然后将其导入到其他脚本中,以方便代码组织。很常用的一种就是创建单独的库文件,里面放一些通用的函数。主要难点在于如何将一个脚本导入另一个脚本中。这里要用到的就是source命令,也就是所谓的点操作符。我们可以在一个脚本中使用source filename导入其他脚本,之所所称为点操作符,因为source filename. filename是等效的。加入有脚本文件名为myshelllib如下:

1
2
3
function test {
echo "my lib function"
}

下面同一目录下的test.sh需要用上面的函数,则test.sh可以如下引入库中的函数:

1
2
source myshelllib  #或者:. myshelllib
test
0%