![高性能Linux服务器运维实战:shell编程、监控告警、性能优化与实战案例](https://wfqqreader-1252317822.image.myqcloud.com/cover/769/33643769/b_33643769.jpg)
2.5 函数以及函数的调用、参数的传递
2.5.1 函数的概念
shell编程中的函数和数学中的函数是不一样的,那么在shell中的函数是什么样的,这里通过一个例子做简单说明。
在Linux中有一个命令是alias,也就是别名的意思,那么下面来实际操作看看这个alias到底有什么用,看如下操作:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/103_01.jpg?sign=1739243534-WRl6EbzJpkQQD6yWjNIyeY5W7GJekd0U-0-8ac77899729e6d0815ced5860cd12e0c)
在以上操作中使用了alias命令后面跟着F=×××,这个F其实就是一个别名。当启动Nginx服务的时候会要求输入绝对路径,这时候可以设置一个别名,相当于F就等于其后面的那条路径,最后只需输入F,就等于执行了启动Nginx的命令。
函数也有类似于别名的作用,简单地说,函数的作用就是将程序里面多次被调用的代码组合起来,称为函数体,并取一个名字称为函数名,当需要用到这段代码的时候,就可以直接来调用函数名。
shell函数类似于shell脚本,里面存放了一系列的指令,不过shell的函数存在于内存中,而不是硬盘中,所以速度很快。另外,shell还能对函数进行预处理,所以函数的启动比脚本更快。
2.5.2 函数定义与语法
在shell中,if语句有它的语法,for循环也有它的语法,那么shell中的函数,也肯定有它的语法,简单来说,有以下两种:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/103_02.jpg?sign=1739243534-ic5ONaFJrfURlWkgFHcDfmFCSmAjKjHs-0-ee12429acaab8db467a8314140b1eb2b)
关键字function表示定义一个函数,可以省略。其后是函数名,有时函数名后可以跟一个括号。符号{表示函数执行命令的入口,该符号也可以在函数名那一行,}表示函数体的结束,两个大括号之间是函数体。
语句部分可以是任意的shell命令,也可以调用其他的函数。如果在函数中使用exit命令,可以退出整个脚本,通常情况下,函数结束之后会返回调用函数的部分继续执行。可以使用break语句来中断函数的执行。
下面看一个简单的例子与解释:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/104_01.jpg?sign=1739243534-wlTTfrjxBFrbSNYkzhvg6kK7bpQCyFvG-0-36dae2a8437fa68f2b96d8c909952ca5)
需要注意的是:函数的定义可以放到.bash_profile文件中,也可以放到使用函数的脚本中,还可以直接放到命令行中,甚至可以使用内部的unset命令删除函数。一旦用户注销,shell将不再保持这些函数。
2.5.3 函数的调用、存储和显示
1.函数的调用
函数定义以后,只需输入函数名即可调用函数,常见函数调用有如下两种形式:
➢ 函数名
➢ 函数名 参数1参数2 …
需要注意的是,函数必须在调用之前定义。下面介绍几个函数调用的实例,注意里面的函数的功能和作用。
首先编写一个脚本function1.sh,内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/104_02.jpg?sign=1739243534-gtWOxg2Z8MPB8jPxu9InucRRCOBibro3-0-d09e19fe226cc64681f94e95f451487f)
这个函数功能非常简单,就是执行echo "hello , you are calling the function"这个命令。执行这个脚本,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/104_03.jpg?sign=1739243534-o3zYzXHgl2qz4FcrNnprrOls1zS3C88y-0-d44f7b11a959feb487b22845ddc39ab5)
继续看第2个例子,脚本function2.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/105_01.jpg?sign=1739243534-ZJMjJWRlFkIRDUOJG7aWktH5hQ8MDy6C-0-f9eed7aab8eca9a90a3ef8a08c02e503)
这个脚本是函数配合select和case一起来使用的,用来输出备份提示信息。执行这个脚本,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/105_02.jpg?sign=1739243534-xHpVcJLxLmy3O3xMLJQArGQ75RIg7gDp-0-dd906ce1e64c6a8c6f21c8e8061e8f20)
这个例子的写法在通过shell脚本进行备份的时候经常用到。
2.函数的存储
函数的存储有两种情况,第1种是函数和调用它的主程序保存在同一个文件中,此时,函数的定义必须出现在调用之前。第2种情况是函数和调用它的主程序保存在不同的文件中,这种情况下,保存函数的文件必须先使用source命令执行,之后才能调用其中的函数。
3.函数的显示
显示当前shell可见的所有函数名,可执行如下命令:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/105_03.jpg?sign=1739243534-XtXzTMlikSewbHB1fbw1bfAKEaQN2Uew-0-0836fb50b6b04d2545a221328fd11ab6)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/106_01.jpg?sign=1739243534-lUjeOAVUTF8yPrh1EI54aP3CtWrsjoux-0-77d23ff54122783db3efc8b55992f240)
显示当前shell可见的所有(指定)函数定义,可执行如下命令:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/106_02.jpg?sign=1739243534-5PZLAcEeTas0Pr4ODTNIsaKH6mUHcr1h-0-96dcad31d0bea609682315c787caaf45)
或者通过指定函数名方式,显示函数定义:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/106_03.jpg?sign=1739243534-nwRVTgD5pCgIyZT8Qg7xTRPVBP5DvETm-0-79a33be8d834d67b3b7f347fd35b1f12)
unset-f可以从shell内存中删除指定的函数,export-f可以将函数输出给shell。
2.5.4 函数与变量以及函数结果与返回值
在函数中可以调用参数(Arguments),可以使用位置参数的形式为函数传递参数。函数内的$1、$2、$3、${n}、$*和$@表示其接收的参数,函数调用结束后位置参数$1、$2、$3、${n}、$*和$@将被重置为调用函数之前的值。在主程序和函数中,$0始终代表脚本名。
在函数内使用local声明的变量是局部(Local)变量,局部变量的作用域是当前函数以及其调用的所有函数;函数内未使用local声明的变量是全局(Global)变量,即主程序和函数中的同名变量是一个变量(地址一致)。
1.函数中参数的传递规则
下面来看一下函数中参数的传递规则,函数可以通过位置变量传递参数,例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_01.jpg?sign=1739243534-JZxBG6JAjWfQ0QgfK7b4qdjjl1zWyi8e-0-57f425bfe0e42e85f8e63f4639685308)
函数执行时,$1对应参数1,其他依次类推。下面看一个实例function3.sh,脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_02.jpg?sign=1739243534-57MiueQG7Xl24SbMY6KakjVwW9t0MN56-0-f2d30e8c89a49c6f9ff6b5dd2c6260ec)
在这个脚本中,参数是通过函数来传递的。执行此脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_03.jpg?sign=1739243534-Gkr4K48RTSMJGGa1jgzqg0L9VBI0rPNB-0-a295eb664b259459f370ae168aaf2c17)
继续看第2个脚本function4.sh,内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_04.jpg?sign=1739243534-avcpIVjqODe5XQ7lRcvcYW7ZMdu9XgUR-0-dfde8617984efd1f4710e71e89ba5f21)
执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_05.jpg?sign=1739243534-pYFh5kJe3cAHbCnfdeNgy2C4dmH49lcO-0-7099921a991a7e524795671fbc050648)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/108_01.jpg?sign=1739243534-GZOPD8KHBJX8ZN33lC4O3JY0iybhuMUc-0-cb8c52e2b1fd70c8912bc4f459b0c4ca)
这个脚本涉及脚本内调用函数、脚本外通过位置参数传递值给函数,以及内部函数之间的互相调用,通过这个脚本的内容和执行结果,可以加深对函数以及传递变量的理解。
再来看最后一个例子function5.sh,内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/108_02.jpg?sign=1739243534-TyzS69bNSpZhbvZDC9234oClzb63om0A-0-ce65461f8ad8fdac5933867f19476da5)
此脚本的功能是进行数字大写的比较。输入的数字是通过参数传递给脚本里面的函数体的,脚本中定义了usage和max两个函数,usage用来对输入的参数做判断,至少两个输入参数,如果小于两个,将给出提示,max用来对输入参数进行大小比较。比较的方法是通过for循环,将较大的值覆盖定义的largest变量。这里注意for循环中省略了in list,所以相当于从输入参数读取循环列表。
执行此脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/108_03.jpg?sign=1739243534-bB4T1cu2yhRNtEZLwFKC8lY0sMM8SNhs-0-6a649f2a61b984947fea0c1887d3dbed)
这里注意,由于largest变量在函数max内没有使用local声明,所以它是全局变量。
2.函数的结果与返回值
当函数的最后一条命令执行结束,函数执行即结束,函数的返回值就是最后一条命令的退出码,其返回值被保存在系统变量$?中,可以使用return或exit显式地结束函数,例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/109_01.jpg?sign=1739243534-qxiON6J3cXTB6amq4mubTAPD7fIvNW4V-0-40d9dcb5190bd0afa1dd7d1d0e638ff1)
函数中的关键字return可以放到函数体的任意位置,shell在执行到return之后,就停止往下执行,返回到主程序的调用行。可以使用N指定函数返回值,return的返回值只能是0~256之间的一个整数。
exit将中断当前函数及当前shell的执行,也可以使用N指定返回值,例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/109_02.jpg?sign=1739243534-v0a1q535YQkXmme40Ax122Kdnc8WvXMx-0-f75a9464421d6abeb943751e8ab08a71)
下面看一个例子function6.sh,内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/109_03.jpg?sign=1739243534-tBwfQpbiXU6GEsiqOjHVOLkZFuQ4ZbaL-0-8250f7c896f19e627a61cdbe5df973a4)
这个脚本功能是判断输入的数字是否可以被2整除,如果可以返回yes ,it is,否则返回no ,it isn't。在这里要注意参数传递,上面read读入的数字,必须加上$符号才能传递给函数。执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/109_04.jpg?sign=1739243534-hQrL3e8RJvTNNpmUYe58bxvXbKAB7nUr-0-862e8864208a39106e8d72002630e3a6)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/110_01.jpg?sign=1739243534-GnQqYnykigSyRBfr2NH8m2a8jrjalcaW-0-c5723e847887000be834d1b192dff98d)
最后再看一个例子,function7.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/110_02.jpg?sign=1739243534-8NDGeQlaqT30WY138fuBCUVaqm8PgdmI-0-6dd09aceda45b8b46157f3deb1e22015)
这个脚本的功能仍然是比较输入的两个数字的大小,它只接受两个数字的比较,在函数体max2中定义了数字比较的方法,并通过return返回较大的数字。首先,通过read读入比较的数字,然后传递给函数体,调用函数比较后,将较大的数字作为状态码返回,通过定义return_val变量获取状态码,继而获取最大的数字。执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/110_03.jpg?sign=1739243534-A08LgZRb2FZOT4VfsGJEKJtxHacYDkwL-0-db968509823e9125559823475daba243)
这个脚本执行了两次,第1次正常输出,第2次执行失败,正常应该输出1988最大,但是发现输出的值为196,出现了问题。当发现shell执行异常的时候,就需要调试排查,此时需要借助sh-x参数,用来输出shell的执行过程,看看哪个步骤出现了问题。执行shell的调试模式,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/111_01.jpg?sign=1739243534-AnDcyYMk4WZfP4wdTD1CnZDpXV28jbKN-0-314dfea08f6d71059f4c0516286fcd7b)
从上面的调试模式看出,倒数第2步出现了问题,可以看到return 1988是正常的,但是return_val获取的就是return的返回值,明明是1988,怎么就变成196了呢?原因很简单,return的返回值只能是0~256之间的一个整数。现在要返回的是1988,明显超过了0~256的范围,所以出错了。也就是说上面这个脚本,只能比对0~256之间的数字的大小,这很明显脚本是有bug的,于是,修改脚本内容为如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/111_02.jpg?sign=1739243534-0YXgfnm6AsWXjGIW6BeffGLFCvZtfRk4-0-f4a71d839fc812dc671ab46c6d4c59d3)
主要变化是将return去掉了,增加了一个largest变量,哪个值大,就把哪个值赋给largest,这样问题就解决了。再次执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/111_03.jpg?sign=1739243534-KfYwSGrZsF3RE8ycF9sB9yRlBupd4ruq-0-86ea91f54097ba36765618876caf2b36)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_01.jpg?sign=1739243534-dYcJFhtfzJGCtPMPUgmVoUJVhJPUwy5R-0-1c7e007029eff1f9f31832d310f2cf4c)
可以看出,现在脚本恢复正常了,可以比较任意大的数字了。
3.分离函数体执行函数的脚本文件
有时候当定义函数过多时,可以把函数写在某一个文件中,这样,当写脚本的时候需要用到某个函数时,就可以直接调用文件中的函数名。怎样将函数写入一个文件中呢?可以执行如下命令:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_02.jpg?sign=1739243534-9yyNsXDrPLlWIJYHf0pJWAo7hj3QYdYl-0-55354cd0024a3ae18d3550cab65a41cc)
以上代码的意思是把下面以EOF开始和结尾的内容导入/etc/init.d/function这个文件中,那么这个文件成为Linux系统内置的脚本函数库,这样,以后就可以做如下调用操作了:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_03.jpg?sign=1739243534-LKa8LkFXhogeBdhYVw7WWkSzLWp48YTc-0-263d02e7e6050219b47f38ff87e2e325)
上面这段代码的意思是:判断/etc/init.d/function如果是一个普通文件,那么就执行./etc/init.d/function,注意,在这里这个.是用来加载function中的命令或者变量参数的;因为在上面定义了zhubo这个函数,那么在最后一行可以直接调用zhubo这个函数,执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_04.jpg?sign=1739243534-ZzgxXJJbkO59X25H91XE9LM1KTwJAGrq-0-fb738f02b700b77c3556f6ab4b466414)
同理,如果有很多函数的话,可以把函数都写到/etc/init.d/function文件中,然后在需要调用的地方直接执行function8.sh脚本的内容,最后加上函数名即可,例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_05.jpg?sign=1739243534-1VKo6YyjtbKhs9j1q05xEX4TZfkl8FNX-0-d1e1da08ceffdd59c1463525ea60b416)
function8.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/113_01.jpg?sign=1739243534-xIUy26DYYCBMSzK83OQVln9yFPGDfyaB-0-a457a8ce91693a617859df30998bd194)
执行function8.sh,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/113_02.jpg?sign=1739243534-o9sDJBU44z9jslvDEI8NsbSj0lZg092S-0-6741bc38dc1bd8dff6f5db3ea5961c94)
这样就实现了分离函数体执行函数的功能。这种功能在系统自带的一些脚本中经常用到。