当前位置: 首页 > Linux

BashTips-详细解释两种定义函数的格式的用法和一些注意事项

时间:2023-04-07 03:16:30 Linux

本文介绍了两种定义函数的格式和一些使用经验:定义函数的两种格式ReturncontentfromfunctionsUsingthefunctionnameasafunctionpointer在函数中执行cd命令的效果将函数中的变量声明为局部变量获取传入函数的所有参数并获取第十个参数定义函数有两种格式在bash中,定义函数时,function关键字是可选的,查看manbash手册,其中提到定义函数的两种格式如下:name()compound-command[redirection]functionname[()]compound-command[redirection]Asyoucan看,不写function关键字时,函数名后面必须加括号(),写function关键字时,括号是可选的。关于compound-command的描述,也可以查看manbash手册,其中提到了以下几种形式:复合命令是以下之一:(list)list在子shell环境中执行。命令完成后,影响shell环境的变量赋值和内置命令不再有效。返回状态是列表的退出状态。{列表;list只是在当前的shell环境中执行。列表必须以换行符或分号结束。返回状态是列表的退出状态。常见的形式是{list;},但也可以写成(list)。例如:$testpwd()(pwd)$testpwd/home/sample/$testpwd()(pwd)$testpwd/home/sample/$testpwd()(pwd;)$testpwd/home/sample/这里定义了一个testpwd函数,它自己的代码就是用括号()括起来的pwd命令,这个命令和()之间可以有也可以没有空格。命令后可以添加也可以不添加分号。注意:当使用{list;},你必须跟在分号';'之后在列表之后,否则会报错。列表之间必须有一个空格;和花括号{在左边。如果写成{list;}会报错,而{list;}则不会报错。建议写成{list;}.例如:$lsfunc(){ls}-bash:意外标记`{ls}'附近出现语法错误$functionlsfunc(){ls;}-bash:意外标记`{ls'附近出现语法错误$lsfunc(){ls;}$lsfunchello.c调用bashshell函数时,不用写括号(),写函数名即可。比如执行上面的lsfunc()函数,直接写lsfunc就可以了。如果写成lsfunc(),它会重新定义这个函数。函数名后可以提供要传入的参数列表,不同的参数之间用空格隔开。如果参数需要空格,请将参数括在引号中。函数的返回内容Bash要求函数的返回值必须是整数,return语句不能用于返回字符串变量。一般来说,整型返回值为0表示函数执行成功,非0表示执行失败。在自定义函数中,执行return语句将退出函数,而不是整个脚本。在函数内执行exit语句将退出整个脚本,而不仅仅是函数。由于函数内部使用了return,所以只能返回整数。如果想从函数内部向函数外部传递一个字符串,可以使用echo命令打印函数内部的字符串,然后调用者通过标准输出得到打印出来的字符串。具体例子如下:$functionfoo(){echo"foo";返回0;}$var=$(foo)$echo${var},$?foo,0可以看到打印出来的结果是"foo,0"。至此,这个函数似乎返回了两个值,一个是通过$(foo)得到foo函数的标准输出,一个是$?通过return语句得到函数返回的0。如果在函数中写成return1,那么上面$?是1。这是另一个例子:$foo(){echo"foo";}$bar(){foo;}$foobar(){a=$(foo);}$变量=$(foo);首先回显:${var}首先:foo$var=$(bar);echo第二个:${var}第二个:foo$var=$(foobar);echothird:${var}third:如您所见,foo函数将字符串Write转换为标准输出,即var=$(foo);语句将foo函数的标准输出分配给var变量,并打印出var变量的值为“foo”。bar()函数调用了foo函数,但是没有读取foo函数打印的标准输出,那么标准输出会被bar函数继承,就好像标准输出是bar函数输出的一样,var=$(酒吧);该语句还将“foo”的值分配给var变量。foob??ar函数读取foo函数的标准输出。foob??ar函数本身并没有使用echo命令来输出内容。这时候如果通过$(foobar)得到函数的输出,它就会变空,因为foo函数中的标准输出是读取到foobar的。注意:这种在函数内部通过echo命令输出字符串的方式有一个缺陷,就是不能再在函数中执行echo语句来打印调试信息。这些调试信息会被函数外的语句一起读取。有用的结果混合调试信息,如果函数外的语句不打印这些结果,就看不到调试信息。执行一个函数后,你可以使用$?表达式获取函数的返回值,但要注意以下情况:var=$(foo)if["$?"=="0"];然后echosuccessfi此时,不要在var=$(foo)和if["$?"之间添加任何语句。=="0"];然后。否则,$?会得到一个不会是$(foo)的返回值,判断会有问题,尤其不要加echo调试语句。也就是说,这种先执行一条语句再判断$?不太靠谱,会受各种影响,所以要特别注意代码语句的顺序。使用函数名作为函数指针在bash中,可以通过以下方法实现类似C语言函数指针的效果。假设有一个test.sh脚本,内容如下:#!/bin/bash#注意有个分号';'$1之后,如果缺少分号,会报错echo_a(){echoaaaa$1;}echo_b(){echobbbb$1;}if["$1"=="-a"];then#这里的echo_a不加双引号echo_common=echo_aelif["$1"=="-b"];那么上面的#echo_a没有加双引号,这里加了。#其实加不加都可以正确执行。echo_common="echo_b"否则回显错误;exit1fi${echo_common}common这个脚本通过echo_common变量保存函数名,相当于一个函数指针,然后使用${echo_common}调用它保存的函数。在bashshell中执行./test.sh-a命令会输出“aaaacommon”;执行./test.sh-b命令将输出“bbbbcommon”。在函数中执行cd命令的影响在函数中执行cd命令。切换到某个目录后,当函数退出时,当前工作目录仍保留在该目录下,不会自动恢复到原来的工作目录。需要手动执行cd-命令才能再次切换回来。假设有一个testcd.sh脚本,脚本内容如下:#!/bin/bashecho"now,pwdis:$(pwd)"cd_to_root(){cd/usr/;}}cd_to_rootecho"afterexecutethecd_to_root,pwdis:$(pwd)"该函数在执行脚本时首先打印工作目录路径,然后执行自定义的cd_to_root函数,在函数内部将工作目录切换到“/usr/”,最后在cd_to_root函数之外打印工作目录路径。执行./testcd.sh脚本,输出如下:[~/sample]$./testcd.shnow,pwd为:/home/sample执行cd_to_root后,pwd为:/usr[~/sample]$pwd/home/sample可以看到如果在函数中执行了cd命令,函数退出后,当前工作目录还是cd之后的目录。但是脚本执行后,当前shell的工作目录还是之前的工作目录,而不是脚本中cd之后的目录。在每个shell下,当前工作目录(workingdirectory)是一个全局状态,一旦改变,就会在整个shell中改变。bash在执行脚本时,会启动一个新的子shell执行,所以在脚本内部执行cd命令会影响运行脚本的子shell的工作目录,但不会影响原来的工作目录母壳。将函数中的变量声明为局部变量在bash中,不使用local命令声明的变量是全局变量,函数内部定义的变量也是全局变量。如果不注意这一点,在函数内操作变量可能会影响同名的外部变量,从而导致意外结果。为了避免影响同名的外部变量,最好将函数中的变量声明为局部变量,使用local命令来声明。检查manbash中本地命令的描述如下:local[option][name[=value]...]对于每个参数,创建一个名为name的局部变量,并赋值。该选项可以是declare接受的任何选项。当在函数中使用local时,它会导致变量名的可见范围仅限于该函数及其子函数。在没有操作数的情况下,local将局部变量列表写入标准输出。不在函数内时使用local是错误的。返回状态为0,除非local在函数外部使用,提供的名称无效,或者名称是只读变量。也就是说,在函数内部,使用local命令声明的变量是局部变量,这些变量在函数外部是不可见的。在函数外,不能使用local命令声明变量,否则会报错。注意:上面解释过,本地命令本身会返回一个值,正常的返回值为0。假设有一个testlocal.sh脚本,内容如下:#!/bin/bashfoo(){return1;}bar(){ret=$(foo);先回声:$?本地var=$(foo);echosecond:$?}foobar(){返回0;}barlocalout=$(foobar);回声第三:$?执行./testlocal.sh脚本,输出:first:1second:0./testlocal.sh:xline:local:只能在函数中使用third:1可见,ret=$(foo);该语句没有使用本地命令定义ret变量,执行foo函数,函数返回值为1,所以得到的$?它是1。但是,本地var=$(foo);语句使用本地命令定义var变量,执行foo函数,得到$?0,但是foo函数明明返回的是1,原因是语句先通过$(foo)执行了foo函数,然后使用localvar将var变量定义为局部变量,所以$?这条语句对应的是本地命令的返回值,不是foo函数的返回值。当本地命令在函数外执行时,会报错。你可以看到虽然foobar函数返回0,但是$?第三条echo语句打印出来的是1,正好是本地命令出错时的返回值。也就是说,对于本地var=$(func);声明,其对应的$?不是被调用函数的返回值,而是本地命令的返回值。那么在执行函数之后,如果要使用$?判断函数的return返回值,注意不要用这种写法。为避免这种情况,最好在函数开头使用local指令声明所有局部变量。使用local声明多个变量时,用空格而不是逗号分隔变量。例子如下:localabc;获取传入函数的所有参数在bash中可以用$1,$2,$3,...,$n来指代传入函数的参数,$1对应第一个参数值,$2对应第二个参数值等等。如果n的值大于9,那么n需要用大括号{}括起来。例如${10}表示获取第10个参数的值,写成$10,则无法获取第10个参数的值。其实$10就相当于${1}0,即先取$1的值,再取0,如果$1的值为“first”,那么$10的值就是“first0”。我们以testparams.sh脚本为例。脚本内容如下:#!/bin/bashfunctionshow_params(){echo$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,${10},${11}}show_params$@这个脚本将传入的参数传递给show_params函数,函数打印出每个参数,用$10和${10}这两种形式来说明它们的区别。./testparams.sh脚本执行结果如下:$./testparams.sh1a2b3c4d5e6f7g8h9i10j11k1a,2b,3c,4d,5e,6f,7g,8h,9i,1a0,10j,11k可以看出传入的第十个参数的值为“10j”,$10打印出来的结果是“1a0”,即第一个参数“1a”后面跟0。打印的结果by${10}是第10个参数的值。相应地,${11}也正确打印了第11个参数的值。$1和$2的写法在bash文档中称为位置参数,中文直译为“位置参数”。检查manbash中的描述如下:位置参数位置参数是一个或多个数字表示的参数,而不是单个数字0。位置参数是在调用shell时从arguments中分配的,并且可以使用设置内置命令。位置参数不能赋值给赋值语句。执行shell函数时临时替换位置参数。当由一个以上的数字组成的位置参数被展开时,必须用大括号参数中的$括起来}参数的值被替换。当参数是一个多于一位的位置参数,或者当参数后跟一个不被解释为它的名称的一部分的字符时,大括号是必需的。参数是shell参数或数组引用(Arrays)。可以看到,上面提到大于9的数字需要用大括号{}括起来,{}的作用是把大括号里面的字符串限制为一个整体。比如有一个var变量值是“Test”,现在你要打印这个变量值,然后打印“Hello”字符串,也就是打印出“TestHello”字符串,然后是获取var的语句变量值和“Hello”字符的字符串中间不能有空格,否则echo命令会将空格一起打印出来,但是写成$varHello是达不到预期效果的。具体例子如下:$var="Test"$echo$varHelloTestHello$echo$varHello$echo${var}HelloTestHello可以看到$var打印出来的“Test”和“Hello”之间有一个空格您好,不是想要的结果。而$varHello打印为空,其实就是获取varHello变量的值。该变量未定义,默认值为空。${var}Hello打印出想要的结果,将var用大括号{}括起来,并明确指定要获取的变量名是var,以免混淆。上面发布的testparams.sh脚本代码也使用了一个$@特殊参数,它本身将扩展为一个位置参数列表。查看manbash的说明如下:SpecialParameters@Expandstothepositionalparameters,从一个开始。当扩展发生在双引号内时,每个参数都会扩展为一个单独的词。即“$@”等同于“$1”“$2”……注意:$@和“$@”的写法结果可能不同。$@扩展为$1$2..."$@"扩展为"$1""$2"...修改上面的testparams.sh脚本,说明$@和"$@"的区别:#!/bin/bashfunctionshow_params(){echo$1,$2,$3}show_params$@show_params"$@"执行testparams.sh脚本,输出如下:$./testparams.sh1a2b3c1a,2b,3c1a,2b,3c$./testparams.sh"1a""2b""3c"1,a,21a,2b,3c$./testparams.sh1a2b1a,2b,1a,2b,你可以看到当传递给脚本的参数值没有空格时$@和"$@"给出相同的结果。当传递给脚本本身的参数值有空格时,$@获取的参数个数会增加,“$@”可以保持参数个数不变。上面的$1就是“1个a”,$@会拆分成“1”和“2”两个参数,然后传递给show_params函数;"$@"将保持不变为"1a",然后传递给show_params函数。即$@对三个参数“1a”“2b”“3c”进行处理后,得到六个参数“1”“a”“2”“b”“3”“c”。“$@”处理后,仍然得到“1a”“2b”“3c”这三个参数。同时我们也可以看到,即使只传入两个参数,$3的引用也不会报错,而是为空。