高性能Linux服务器运维实战:shell编程、监控告警、性能优化与实战案例
上QQ阅读APP看书,第一时间看更新

2.5 函数以及函数的调用、参数的传递

2.5.1 函数的概念

shell编程中的函数和数学中的函数是不一样的,那么在shell中的函数是什么样的,这里通过一个例子做简单说明。

在Linux中有一个命令是alias,也就是别名的意思,那么下面来实际操作看看这个alias到底有什么用,看如下操作:

在以上操作中使用了alias命令后面跟着F=×××,这个F其实就是一个别名。当启动Nginx服务的时候会要求输入绝对路径,这时候可以设置一个别名,相当于F就等于其后面的那条路径,最后只需输入F,就等于执行了启动Nginx的命令。

函数也有类似于别名的作用,简单地说,函数的作用就是将程序里面多次被调用的代码组合起来,称为函数体,并取一个名字称为函数名,当需要用到这段代码的时候,就可以直接来调用函数名。

shell函数类似于shell脚本,里面存放了一系列的指令,不过shell的函数存在于内存中,而不是硬盘中,所以速度很快。另外,shell还能对函数进行预处理,所以函数的启动比脚本更快。

2.5.2 函数定义与语法

在shell中,if语句有它的语法,for循环也有它的语法,那么shell中的函数,也肯定有它的语法,简单来说,有以下两种:

关键字function表示定义一个函数,可以省略。其后是函数名,有时函数名后可以跟一个括号。符号{表示函数执行命令的入口,该符号也可以在函数名那一行,}表示函数体的结束,两个大括号之间是函数体。

语句部分可以是任意的shell命令,也可以调用其他的函数。如果在函数中使用exit命令,可以退出整个脚本,通常情况下,函数结束之后会返回调用函数的部分继续执行。可以使用break语句来中断函数的执行。

下面看一个简单的例子与解释:

需要注意的是:函数的定义可以放到.bash_profile文件中,也可以放到使用函数的脚本中,还可以直接放到命令行中,甚至可以使用内部的unset命令删除函数。一旦用户注销,shell将不再保持这些函数。

2.5.3 函数的调用、存储和显示

1.函数的调用

函数定义以后,只需输入函数名即可调用函数,常见函数调用有如下两种形式:

➢ 函数名

➢ 函数名 参数1参数2 …

需要注意的是,函数必须在调用之前定义。下面介绍几个函数调用的实例,注意里面的函数的功能和作用。

首先编写一个脚本function1.sh,内容如下:

这个函数功能非常简单,就是执行echo "hello , you are calling the function"这个命令。执行这个脚本,结果如下:

继续看第2个例子,脚本function2.sh内容如下:

这个脚本是函数配合select和case一起来使用的,用来输出备份提示信息。执行这个脚本,结果如下:

这个例子的写法在通过shell脚本进行备份的时候经常用到。

2.函数的存储

函数的存储有两种情况,第1种是函数和调用它的主程序保存在同一个文件中,此时,函数的定义必须出现在调用之前。第2种情况是函数和调用它的主程序保存在不同的文件中,这种情况下,保存函数的文件必须先使用source命令执行,之后才能调用其中的函数。

3.函数的显示

显示当前shell可见的所有函数名,可执行如下命令:

显示当前shell可见的所有(指定)函数定义,可执行如下命令:

或者通过指定函数名方式,显示函数定义:

unset-f可以从shell内存中删除指定的函数,export-f可以将函数输出给shell。

2.5.4 函数与变量以及函数结果与返回值

在函数中可以调用参数(Arguments),可以使用位置参数的形式为函数传递参数。函数内的$1、$2、$3、${n}、$*和$@表示其接收的参数,函数调用结束后位置参数$1、$2、$3、${n}、$*和$@将被重置为调用函数之前的值。在主程序和函数中,$0始终代表脚本名。

在函数内使用local声明的变量是局部(Local)变量,局部变量的作用域是当前函数以及其调用的所有函数;函数内未使用local声明的变量是全局(Global)变量,即主程序和函数中的同名变量是一个变量(地址一致)。

1.函数中参数的传递规则

下面来看一下函数中参数的传递规则,函数可以通过位置变量传递参数,例如:

函数执行时,$1对应参数1,其他依次类推。下面看一个实例function3.sh,脚本内容如下:

在这个脚本中,参数是通过函数来传递的。执行此脚本,输出如下:

继续看第2个脚本function4.sh,内容如下:

执行这个脚本,输出如下:

这个脚本涉及脚本内调用函数、脚本外通过位置参数传递值给函数,以及内部函数之间的互相调用,通过这个脚本的内容和执行结果,可以加深对函数以及传递变量的理解。

再来看最后一个例子function5.sh,内容如下:

此脚本的功能是进行数字大写的比较。输入的数字是通过参数传递给脚本里面的函数体的,脚本中定义了usage和max两个函数,usage用来对输入的参数做判断,至少两个输入参数,如果小于两个,将给出提示,max用来对输入参数进行大小比较。比较的方法是通过for循环,将较大的值覆盖定义的largest变量。这里注意for循环中省略了in list,所以相当于从输入参数读取循环列表。

执行此脚本,输出如下:

这里注意,由于largest变量在函数max内没有使用local声明,所以它是全局变量。

2.函数的结果与返回值

当函数的最后一条命令执行结束,函数执行即结束,函数的返回值就是最后一条命令的退出码,其返回值被保存在系统变量$?中,可以使用return或exit显式地结束函数,例如:

函数中的关键字return可以放到函数体的任意位置,shell在执行到return之后,就停止往下执行,返回到主程序的调用行。可以使用N指定函数返回值,return的返回值只能是0~256之间的一个整数。

exit将中断当前函数及当前shell的执行,也可以使用N指定返回值,例如:

下面看一个例子function6.sh,内容如下:

这个脚本功能是判断输入的数字是否可以被2整除,如果可以返回yes ,it is,否则返回no ,it isn't。在这里要注意参数传递,上面read读入的数字,必须加上$符号才能传递给函数。执行这个脚本,输出如下:

最后再看一个例子,function7.sh脚本内容如下:

这个脚本的功能仍然是比较输入的两个数字的大小,它只接受两个数字的比较,在函数体max2中定义了数字比较的方法,并通过return返回较大的数字。首先,通过read读入比较的数字,然后传递给函数体,调用函数比较后,将较大的数字作为状态码返回,通过定义return_val变量获取状态码,继而获取最大的数字。执行这个脚本,输出如下:

这个脚本执行了两次,第1次正常输出,第2次执行失败,正常应该输出1988最大,但是发现输出的值为196,出现了问题。当发现shell执行异常的时候,就需要调试排查,此时需要借助sh-x参数,用来输出shell的执行过程,看看哪个步骤出现了问题。执行shell的调试模式,输出如下:

从上面的调试模式看出,倒数第2步出现了问题,可以看到return 1988是正常的,但是return_val获取的就是return的返回值,明明是1988,怎么就变成196了呢?原因很简单,return的返回值只能是0~256之间的一个整数。现在要返回的是1988,明显超过了0~256的范围,所以出错了。也就是说上面这个脚本,只能比对0~256之间的数字的大小,这很明显脚本是有bug的,于是,修改脚本内容为如下:

主要变化是将return去掉了,增加了一个largest变量,哪个值大,就把哪个值赋给largest,这样问题就解决了。再次执行这个脚本,输出如下:

可以看出,现在脚本恢复正常了,可以比较任意大的数字了。

3.分离函数体执行函数的脚本文件

有时候当定义函数过多时,可以把函数写在某一个文件中,这样,当写脚本的时候需要用到某个函数时,就可以直接调用文件中的函数名。怎样将函数写入一个文件中呢?可以执行如下命令:

以上代码的意思是把下面以EOF开始和结尾的内容导入/etc/init.d/function这个文件中,那么这个文件成为Linux系统内置的脚本函数库,这样,以后就可以做如下调用操作了:

上面这段代码的意思是:判断/etc/init.d/function如果是一个普通文件,那么就执行./etc/init.d/function,注意,在这里这个.是用来加载function中的命令或者变量参数的;因为在上面定义了zhubo这个函数,那么在最后一行可以直接调用zhubo这个函数,执行这个脚本,输出如下:

同理,如果有很多函数的话,可以把函数都写到/etc/init.d/function文件中,然后在需要调用的地方直接执行function8.sh脚本的内容,最后加上函数名即可,例如:

function8.sh脚本内容如下:

执行function8.sh,结果如下:

这样就实现了分离函数体执行函数的功能。这种功能在系统自带的一些脚本中经常用到。