- 高性能Linux服务器运维实战:shell编程、监控告警、性能优化与实战案例
- 高俊峰编著
- 3063字
- 2025-02-16 06:51:02
2.5 函数以及函数的调用、参数的传递
2.5.1 函数的概念
shell编程中的函数和数学中的函数是不一样的,那么在shell中的函数是什么样的,这里通过一个例子做简单说明。
在Linux中有一个命令是alias,也就是别名的意思,那么下面来实际操作看看这个alias到底有什么用,看如下操作:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/103_01.jpg?sign=1739660347-cdsCHP12jBoqRTnFd3SKSs0oJLTNGzoT-0-4fcae02a3a1f23530f7a2b19f51ea407)
在以上操作中使用了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=1739660347-A2IdZUHU3orl7QVGYpkK9EnRs1RZoy3L-0-66b7e6aecee928c0e672dd84a81e4970)
关键字function表示定义一个函数,可以省略。其后是函数名,有时函数名后可以跟一个括号。符号{表示函数执行命令的入口,该符号也可以在函数名那一行,}表示函数体的结束,两个大括号之间是函数体。
语句部分可以是任意的shell命令,也可以调用其他的函数。如果在函数中使用exit命令,可以退出整个脚本,通常情况下,函数结束之后会返回调用函数的部分继续执行。可以使用break语句来中断函数的执行。
下面看一个简单的例子与解释:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/104_01.jpg?sign=1739660347-pOh4wpVdE9zsmSuallzqZOr0JOESJKJw-0-1753c75e442e6e3993733f729eff938b)
需要注意的是:函数的定义可以放到.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=1739660347-pVJIMjrHd2ibc8HUdLhJMhszZm4jIx7x-0-938760e894fe362c67a91aea3abab29a)
这个函数功能非常简单,就是执行echo "hello , you are calling the function"这个命令。执行这个脚本,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/104_03.jpg?sign=1739660347-HMY6Dn27aqG5GlJAXWvDyN5PO79rIzGG-0-0f027c696e502fc733e2da2b00361579)
继续看第2个例子,脚本function2.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/105_01.jpg?sign=1739660347-aWXtdOQPnfqr0DVEhqEtHAm6y7tOaOwY-0-9bef9fe057f39a5837051aea9dcf864b)
这个脚本是函数配合select和case一起来使用的,用来输出备份提示信息。执行这个脚本,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/105_02.jpg?sign=1739660347-LX3FLCTXOIci2ieGB261hrAGFKJToIfX-0-8ef96688a50ce53b021bd8fd4ebde709)
这个例子的写法在通过shell脚本进行备份的时候经常用到。
2.函数的存储
函数的存储有两种情况,第1种是函数和调用它的主程序保存在同一个文件中,此时,函数的定义必须出现在调用之前。第2种情况是函数和调用它的主程序保存在不同的文件中,这种情况下,保存函数的文件必须先使用source命令执行,之后才能调用其中的函数。
3.函数的显示
显示当前shell可见的所有函数名,可执行如下命令:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/105_03.jpg?sign=1739660347-ujrf725J3Xpf3bfOP8PqLnEvtT0JIM8O-0-96ef07bd328d42a8b1024d1d697358fc)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/106_01.jpg?sign=1739660347-BJFyYVMW3EBOqp0PIKo5G9X77stz8ibE-0-aba4a8114f735b5434f3ab7b570cd73f)
显示当前shell可见的所有(指定)函数定义,可执行如下命令:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/106_02.jpg?sign=1739660347-nGGHwYZEiOG6TbZ7nC66Wir2taAMyUd0-0-a6ecc4cba848d47d650a2bdf7b273346)
或者通过指定函数名方式,显示函数定义:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/106_03.jpg?sign=1739660347-eReYEx2F4B9ncAsoqd3NcNCjCYIMIf19-0-363d99b9a91bedc1205f36a95b8f3676)
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=1739660347-zlTVAJ0IE4Vqg14J6hdkexyyoPBZoHCZ-0-8572ae94463e72339600d08986d7d8ca)
函数执行时,$1对应参数1,其他依次类推。下面看一个实例function3.sh,脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_02.jpg?sign=1739660347-wFW6cjjZzdEyZan8qaQuKe3ZzlGbHsI3-0-cc6908c156295a08b5e7ed4f3f8793f1)
在这个脚本中,参数是通过函数来传递的。执行此脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_03.jpg?sign=1739660347-cEkek6x2TXkCApenWoBiCT2jxfYVPXAX-0-e2a83213698ddf95df45cdb3792f4ed5)
继续看第2个脚本function4.sh,内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_04.jpg?sign=1739660347-pQZ9yKghy5xV5tOi25kFbtpT289GjYEW-0-57e36db3652790a73d713108824d3446)
执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/107_05.jpg?sign=1739660347-wRqqIZt2Au9WaEgkFNauQzqTMWWwxTOM-0-0b864e83216bf50d5c108baebe9fd968)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/108_01.jpg?sign=1739660347-JQf2QOiM9HgT4WjH5RC0MxeWPZnqVkLM-0-d0fbed1f311f58eb9b1517a0d0cc26e1)
这个脚本涉及脚本内调用函数、脚本外通过位置参数传递值给函数,以及内部函数之间的互相调用,通过这个脚本的内容和执行结果,可以加深对函数以及传递变量的理解。
再来看最后一个例子function5.sh,内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/108_02.jpg?sign=1739660347-jIf51UjiVqBPTdJl7GwyvQa41ZrXZD4d-0-b0b3e5c54825fe940ae02a893c5fa031)
此脚本的功能是进行数字大写的比较。输入的数字是通过参数传递给脚本里面的函数体的,脚本中定义了usage和max两个函数,usage用来对输入的参数做判断,至少两个输入参数,如果小于两个,将给出提示,max用来对输入参数进行大小比较。比较的方法是通过for循环,将较大的值覆盖定义的largest变量。这里注意for循环中省略了in list,所以相当于从输入参数读取循环列表。
执行此脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/108_03.jpg?sign=1739660347-RZUPXrlJm1eBzrX1HBhaV7sJXoQGexJg-0-06a5e16fca2d2a1e4b87e50aefa4ee0c)
这里注意,由于largest变量在函数max内没有使用local声明,所以它是全局变量。
2.函数的结果与返回值
当函数的最后一条命令执行结束,函数执行即结束,函数的返回值就是最后一条命令的退出码,其返回值被保存在系统变量$?中,可以使用return或exit显式地结束函数,例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/109_01.jpg?sign=1739660347-XMzrllseRTeL5d5paMWxZfWPimiJd197-0-6cc63d412e6ba84dffd177fba00f9595)
函数中的关键字return可以放到函数体的任意位置,shell在执行到return之后,就停止往下执行,返回到主程序的调用行。可以使用N指定函数返回值,return的返回值只能是0~256之间的一个整数。
exit将中断当前函数及当前shell的执行,也可以使用N指定返回值,例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/109_02.jpg?sign=1739660347-5DecHRMYMX69qPiGp4xJZ0pGoSk8e2P5-0-db5f4f33f3c17948b7d0c2bb900296e7)
下面看一个例子function6.sh,内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/109_03.jpg?sign=1739660347-prWiKM6xL2zVVpT0Ik6HhkE4l0Vmu0Bh-0-6a923b70ad9f924686555bb7d51ff42c)
这个脚本功能是判断输入的数字是否可以被2整除,如果可以返回yes ,it is,否则返回no ,it isn't。在这里要注意参数传递,上面read读入的数字,必须加上$符号才能传递给函数。执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/109_04.jpg?sign=1739660347-J7ZLPi0b2lpMCFYrzNPcLIHyFNkjB5g4-0-c361d361ba60cad9a14f3f7c9e83cc69)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/110_01.jpg?sign=1739660347-HBm4wlJG4LtJGmVSIIKWGzgJPf1fvkP2-0-f3b9af442b4b4896a133b6672d0ba7fb)
最后再看一个例子,function7.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/110_02.jpg?sign=1739660347-oXdEnN57PoQJccqI8QCVlJZBYs8RdZCi-0-080c213a2dd2e55aea57c257794dcd5b)
这个脚本的功能仍然是比较输入的两个数字的大小,它只接受两个数字的比较,在函数体max2中定义了数字比较的方法,并通过return返回较大的数字。首先,通过read读入比较的数字,然后传递给函数体,调用函数比较后,将较大的数字作为状态码返回,通过定义return_val变量获取状态码,继而获取最大的数字。执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/110_03.jpg?sign=1739660347-jWBqub10o0YHSsjua5UNjnRDJNr6Y9oE-0-0d3e13750a4345dce29d3fa562a44cb5)
这个脚本执行了两次,第1次正常输出,第2次执行失败,正常应该输出1988最大,但是发现输出的值为196,出现了问题。当发现shell执行异常的时候,就需要调试排查,此时需要借助sh-x参数,用来输出shell的执行过程,看看哪个步骤出现了问题。执行shell的调试模式,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/111_01.jpg?sign=1739660347-abHSTOC6kwGKBdkBdLQ2qETEfKFg8L5E-0-f100df9dac6c350d3319fd5c9f891e14)
从上面的调试模式看出,倒数第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=1739660347-2U8RChUoLRvc8Cgl12aZ7IOph8ysGD4n-0-8f080d142a1fa34455fac5cd71e19475)
主要变化是将return去掉了,增加了一个largest变量,哪个值大,就把哪个值赋给largest,这样问题就解决了。再次执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/111_03.jpg?sign=1739660347-zLJKpUlS9ai578X3rSkdh7Rn5dyhnzaZ-0-6b14b7af99b1a45f8f026ca24d3a6096)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_01.jpg?sign=1739660347-wV3fyqx7CafujOkG4IVBS5AnMDG8hTc2-0-de0892198a525e9f665e7ff7a74753ee)
可以看出,现在脚本恢复正常了,可以比较任意大的数字了。
3.分离函数体执行函数的脚本文件
有时候当定义函数过多时,可以把函数写在某一个文件中,这样,当写脚本的时候需要用到某个函数时,就可以直接调用文件中的函数名。怎样将函数写入一个文件中呢?可以执行如下命令:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_02.jpg?sign=1739660347-mjNtgc8CwtSjv1wzXvd38EBLw5EBgnaO-0-04f18944edb1407adeb5ccec33a4b91e)
以上代码的意思是把下面以EOF开始和结尾的内容导入/etc/init.d/function这个文件中,那么这个文件成为Linux系统内置的脚本函数库,这样,以后就可以做如下调用操作了:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_03.jpg?sign=1739660347-qJSjpFafqHAk0I97NSMBuViLiBij0oYb-0-e5ff25b6cb65c9eed1b490d721f93dd7)
上面这段代码的意思是:判断/etc/init.d/function如果是一个普通文件,那么就执行./etc/init.d/function,注意,在这里这个.是用来加载function中的命令或者变量参数的;因为在上面定义了zhubo这个函数,那么在最后一行可以直接调用zhubo这个函数,执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_04.jpg?sign=1739660347-MVoeV6bPWqRXTSmWAAqF1Zbh0K4YSFsJ-0-d6d7e0b248f06a37865a07551b491a54)
同理,如果有很多函数的话,可以把函数都写到/etc/init.d/function文件中,然后在需要调用的地方直接执行function8.sh脚本的内容,最后加上函数名即可,例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/112_05.jpg?sign=1739660347-TZf9nWXOVMjWYKGfFn2RCkCi3csfbob1-0-67b70c9f05289a0a83da156bb013e321)
function8.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/113_01.jpg?sign=1739660347-q8Jwru4Ds9vZ5wuZHOlqVZnyvIcxYvWT-0-4eeaaf7dd0d51686f4764f535eaec150)
执行function8.sh,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/113_02.jpg?sign=1739660347-hugY6oraguqJ5LzUS1xFA5cT3fhhyYXy-0-66c2f9932d17c7ef72f24110ccffe96b)
这样就实现了分离函数体执行函数的功能。这种功能在系统自带的一些脚本中经常用到。