Shell(十):函数
函数可以将大的命令集合分解成若干个较小的任务,可基于函数进一步构造更复杂的Shell程序,而不需要重复编写相同那个的代码。在Linux Shell中,所有函数的定义都是平行的,即不允许在函数体内再定义其他的函数,但允许函数之间相互调用。
1、函数的定义
Linux Shell也有函数,但对其实现做了某些限制,如,在Linux Shell中函数的返回值只能为退出状态0或1。函数是一串命令的集合,若在脚本中有重复代码时,可考虑使用函数。
Shell函数的基本格式:
function_name(){
command1
...
commandN
}
函数由 函数名 和 函数体 构成。函数体是函数内的命令集合,函数名应该唯一,若不唯一,脚本执行时会产生错误。
在函数名前可以加上关键字function,但加上和省略关键字function对脚本的最终执行不产生任何影响。函数之间可以通过参数、函数返回值通信,函数在脚本中出现的次序可以是任意的,但是必须按照脚本中的调用次序执行这些函数。
新建 func01.sh 脚本,详情如下:
#!/bin/bash
# 创建函数
hello(){
echo "Hello Shell Function"
}
# 调用hello函数
echo "invoke hello() start "
hello
# 提示结束函数调用
echo "invoke hello() end "
脚本 func01.sh 是从顶部开始执行,当遇见"hello()"结构时,知道定义了一个名为hello的函数,会记住hello代表着一个函数,并执行函数体中的命令。直到出现"}"字符,因为"}"代表了函数体结束。当执行到单独的行 hello 时,Shell就知道应该去执行刚才定义的函数了,当这个函数执行完毕之后,执行过程会返回到改行后面继续执行其他命令或函数。
在Shell中不需要声明就可直接定义函数,但是在调用函数前需要对它进行定义。由于所有的脚本程序都是从顶部开始执行的,所以,需要首先定义函数,然后才能对函数进行调用,以此来保证函数能被正常使用。新建 func02.sh 脚本,详情如下:
#!/bin/bash # 循环中调用函数 # 在一行中显示1 2 3 4 5 output(){ for (( num=1; num <=5; num++ )) do echo -n "$num " done } # 在循环中调用函数 let "num2=1" while [ "$num2" -le 5 ] # 执行循环体 do output # 调用函数 echo "" # 换行 let "num2=num2+1" done
执行结果如下:
判断当前目录下存在多少个文件和子目录是Shell编程经常遇到的情况之一。新建脚本 func03.sh 脚本,详情如下:
#!/bin/bash
# 判断当前目录存在多少个文件和多少个子目录
directory() {
let "filenum = 0"
let "dirnum = 0"
# 显示当前目录下所有的子目录和文件,并使用 echo 换行
ls $1
echo ""
# 使用 for 循环判断当前子目录和文件的个数
for file in $( ls )
do
if [ -d $file ]
then
# 判断为子目录,目录个数加1
let "dirnum = dirnum + 1"
else
# 判断为文件,文件个数加1
let "filenum = filenum + 1"
fi
done
# 使用echo命令显示当前目录下的子目录和文件个数
echo "The number of directories is $dirnum"
echo "The number of files is $filenum"
}
# 脚本中调用函数
directory
func03.sh中定义了directory函数,该函数首先定义了两个变量filenum和dirnum,用于记录文件的个数和子目录个数,然后通过ls命令显示当前目录下的文件和子目录,通过for循环逐个调用当前目录下的文件和子目录,接着通过-d命令判断是否是目录,若是,则dirnum加1,否则filenum加1,通过不断的循环,就可得出当前目录下文件的个数和子目录个数。最后调用函数directory。执行结果如下:
2、向函数传递参数
在bash Shell编程中,向函数传递的参数仍然是以位置参数的方式来传递的,而不能传递数组等其他形式变量。
新建 func04.sh 脚本,说明如何向函数传递参数,以及传递前后数值的变化情况,详情如下:
#!/bin/bash
# 说明函数如何传递参数和传值前后的变量值如何变化
half(){
# 将参数传递给n
let "n = $1"
# 让 n 的值减半
let "n = n/2"
echo "In function half() n is $n"
}
# 函数调用
let "m=$1"
# 显示函数调用前m值
echo "Before the function half() is called, m is $m"
# 显示函数调用时m值
half $m
# 显示函数调用后m值
echo "After the function half() is called, m is $m"
func04.sh 脚本定义half函数,half函数可以带一个参数$1,$1在half函数内减半;接着,func04.sh脚本分别输出传递half函数的参数在函数调用前、调用时以及调用后的结果。执行结果如下:
脚本func04.sh中调用函数half,参数m值为10,传递给函数half中的n,n复制m的值,故n的初值是10,经除2操作后,n的值1改为为5,但是m在函数执行后的值并没有随着n值的改变而改变,其值仍然为10。
由脚本 func04.sh 的执行结果可以看出,函数的参数复制的是调用时对应的参数值,而函数通过一些命令可以对参数进行运算或处理,仅改变函数内参数的值,但不会影响原参数的值。
新建 func05.sh 脚本,详情如下:
#!/bin/bash
# 函数实现两数加、减、乘和除四则运算
count(){
# 判断参数个数是否不等于3,不等于3表示输入参数错误
if [ $# -ne 3 ]
then
# 提示输入参数个数错误
echo "The number of args is not 3!"
fi
let "s = 0"
case $2 in
+) # 加操作
let "s = $1 + $3"
echo "$1 + $3 = $s";;
-) # 减操作
let "s = $1 - $3"
echo "$1 - $3 = $s";;
\*) # 乘操作
let "s = $1 * $3"
echo "$1 * $3 = $s";;
\/) # 除操作
let "s = $1 / $3"
echo "$1 / $3 = $s";;
*)
echo "What you input is wrong! ";;
esac
}
# 提示输入的
echo "Please type your word: ( e.g. 1 + 1 ) "
# 读取输入的参数
read a b c
count $a $b $c
在 func05.sh 中,定义了一个函数count,该函数用于判断输入的参数是否是3个,若不是,则输出"The number of args is not 3!",表示参数输入个数不正确,当输入的参数正确时,将执行case语句,该语句通过判断第二参数是加、减、乘、除中的哪种运算来执行不同的操作,若参数格式输入不正确,则输出语句"What you input is wrong!"。在函数count外通过"Please type your word:( e.g. 1 + 1 )"提示用户输入三个参数,通过read命令读取三个参数,最后调用函数count和三个参数来产生输出。
脚本 func05.sh 执行结果如下:
在脚本 func05.sh 中,函数count通过判断第二个参数是加、减、乘、除的哪个运算符来执行不同的操作。在执行该脚本的过程中,要注意参数与参数之间需用空格隔开,否则就会将其视为一个参数而产生错误。
在Linux Shell编程中,函数还可传递间接参数。新建 func06.sh 脚本,详情如下:
#!/bin/bash
# 表明函数如何传递间接参数
# 用于显示参数值
ind_func(){
echo " $1"
}
# 设置间接参数
parameter=ind_arg
ind_arg=Hello
# 显示直接的参数
ind_func "$parameter"
# 显示间接参数
ind_func "${!parameter}"
echo "*************************************"
# 改变 ind_arg 值后的情况
ind_arg=World
ind_func "$parameter"
ind_func "${!parameter}"
在脚本 func06.sh 中,ind_func函数用于输出参数。在执行执行 func06.sh 脚本的过程中,将变量ind_arg赋值给了parameter,同时将变量hello赋值给了ind_arg,然后通过调用函数ind_func输出直接参数parameter的值,再次调用函数ind_arg使用变量${!parameter}输出parameter的间接参数值。接着改变ind_arg的值为world,可以看出,parameter变量的间接参数值也随之发生了改变。
脚本 func06.sh,执行结果如下:
3、函数返回值
有时需要脚本执行完成后返回特定的值来完成脚本的后继操作,这些特定的值就是函数返回值。在Linux Shell编程中,函数通过return返回其提出状态,0表示无错误,1表示有错误。在脚本中可以有选择的使用return语句,因为函数在执行完最后一条语句后将执行调用该函数的地方执行后续操作。
新建 func07.sh 脚本,详情如下:
#!/bin/bash
# 根据用户输入显示星期
# 使用return语句的函数
show_week(){
echo -n "What you input is: "
echo "$1"
# 根据输入的参数值来显示不同的操作
case $1 in
0)
echo "Today is Sunday. "
return 0;;
1)
echo "Today is Monday. "
return 0;;
2)
echo "Today is Tuesday. "
return 0;;
3)
echo "Today is Wednesday. "
return 0;;
4)
echo "Today is Thursday. "
return 0;;
5)
echo "Today is Friday. "
return 0;;
6)
echo "Today is Saturday. "
return 0;;
*)
return 1;;
esac
}
# 主程序部分根据返回值不同输出不同的结果
if show_week "$1"
then
echo "What you input is right! "
else
echo "What you input is wrong!"
fi
exit 0
show_week函数使用return语句,return语句返回值为0时,表示执行函数show_week时输入的命令行参数是正确的;当return语句不为0时,表示执行函数show_week时输入的命令行参数是错误的。在函数外通过if/else语句判断来显示不同函数的返回值对应不同的输出。
脚本 func07.sh 执行结果如下:
4、函数调用
在Linux Shell脚本中,可以同时放置多个函数,函数之间允许相互调用,而且允许一个函数调用多个函数。
4.1、脚本放置多个函数
脚本中可放置多个函数,脚本执行时按照调用函数的顺序执行这些函数。新建 func07.sh 脚本,内容如下:
#!/bin/bash # 脚本中放置多个函数 # 该函数在一行显示1~7 show_week(){ for day in Monday Tuesday Wednesday Thursday Friday Saturday Sunday do echo -n "$day " done echo "" # 换行 } # 用于显示 1~7 的平方 show_number(){ for (( integer=1; integer<=7; integer++ )) do echo -n "$integer " done echo "" # 换行 } # 实现 1~7 的平方运算和结果输出 show_square(){ i=0 until [[ "$i" -gt 7 ]] do let "square=i*i" echo "$i * $i = $square" let "i++" done echo "" # 换行 } # 顺序执行函数 show_week echo "--------------------------" show_number echo "--------------------------" show_square
执行结果如下:
4.2、函数相互调用
Shell编程中,函数可相互调用,调用时会停止当前运行的函数转去运行被调用的函数,直到调用的函数运行完,再返回当前函数继续运行。
新建 func09.sh 脚本,内容如下:
#/bin/bash # 函数相互调用 # 函数执行显示输入参数的平方 square(){ echo "Please input the num:" read num let "square=num * num" echo "Square of $num is $square." } # 函数执行显示输入参数的立方 cube(){ echo "Please input the num2:" read num2 let "cube=num2 * num2 * num2" echo "Cube of $num2 is $cube." } # 函数执行显示输入参数的幂次方 power(){ echo "Please input the num3:" read num3 echo "Please input the power:" read p let "temp = 1" for (( i=1; i<=$p; i++ )) do let "temp=temp*num3" done echo "power $p of $num3 is $temp." } # 选择调用的函数 choice(){ echo "请输入操作符(s-平方,c-立方,p-幂次方):" read char case $char in s) square;; c) cube;; p) power;; *) echo "What you input is wronng!";; esac } # 调用函数choice choice
在 func09.sh 脚本中,定了四个函数,square用于计算平方运算,cube函数用于计算立方运算,power函数用于计算幂运算,而choice函数通过case语句进行参数选择调用其他三个函数中的一个,根据输入参数的不通过而执行不同的操作。
脚本 func09.sh 执行结果如下:
4.3、一个函数调用多个函数
Shell编程中,允许一个函数调用多个函数,在该函数调用其他函数同样需要按照调用的顺序来执行调用的函数。
新建 func10.sh 脚本,内容如下:
#!/bin/bash
# 用于显示一个不多于5位正整数的位数,并按顺序显示各个数位的值
count_of_int(){
# 数值大于9999时,表示该数为5位数
if [ $1 -gt 9999 ]
then
let "place=5"
# 大于 999 小于 9999,该数为4位数
elif [ $1 -gt 999 ]
then
let "place=4"
# 大于 99 小于 999,该数为3位数
elif [ $1 -gt 99 ]
then
let "place=3"
# 大于 9 小于 99,该数为2位数
elif [ $1 -gt 9 ]
then
let "place=2"
else
let "place=1"
fi
# 输出该数的位数
echo "The place of the $1 is $place."
}
# 该函数实现显示该整数每个数位上的数字
num_of_int(){
let "ten_thousand=$1/10000"
let "thousand=$1/1000%10"
let "hundred=$1/100%10"
let "ten=$1%100/10"
let "indiv=$1%10"
# 当输入万位上的数不等于0时,表示该数为5位数,需输出 万、千、百、十、个位
if [ $ten_thousand -ne 0 ]
then
echo "$ten_thousand $thousand $hundred $ten $indiv"
# 当输入万位上的数等于0而千位不等于0时,表示该数为4位数,需输出 千、百、十、个位
elif [ $thousand -ne 0 ]
then
echo "$thousand $hundred $ten $indiv"
# 当输入万位和千位的数等于0而百位不等于0时,表示该数为4位数,需输出 百、十、个位
elif [ $hundred -ne 0 ]
then
echo "$hundred $ten $indiv"
# 当输入万、千、百位的数等于0而十位不等于0时,表示该数为4位数,需输出 十、个位
elif [ $ten -ne 0 ]
then
echo "$ten $indiv"
# 输出个位数
else
echo "$indiv"
fi
}
show() {
echo "Pls input the number(1-99999):"
read num
count_of_int $num # 显示输入参数的位数
num_of_int $num # 显示输入参数的各数位值
}
# 调用函数
show
func10.sh 中定义了三个函数,其中函数 count_of_int用于显示输入参数的位数,函数num_of_int用于显示输入参数各位数的值,而函数show则同时调用函数count_of_int和函数num_of_int。该脚本是从调用show函数开始执行,在执行show函数时首先通过echo语句提示输入1~99999内的一个数,然后调用函数count_of_num显示命令行参数的位数,最后调用函数num_of_int显示命令行1参数的各数位的值。
func10.sh 执行结果如下:
5、局部变量和全局变量
Shell编程中,通过local关键字在Shell函数中声明局部变量,局部变量将局限在函数范围内。函数也可调用函数外的全局变量,若一个局部变量和一个全局变量的名字相同,则在函数中局部变量将会覆盖掉全局变量。
新建 func11.sh 脚本,内容如下:
#!/bin/bash
# 调用局部变量和全局变量
text="global variable"
# 函数中使用的局部变量和全局变量的名字相同
use_local_var_fun(){
local text="local variable"
echo "In function user_local_var_fun:$text"
}
#输出函数 use_local_var_fun 内的局部变量值
echo "Execute the function use_local_var_fun"
use_local_var_fun
# 输出函数 use_local_var_fun 外的全局变量值
echo "Out of function use_local_var_fun:$text"
exit 0
在 func11.sh 脚本中,在函数外定义了全局变量 text,接着在函数use_local_var_fun内定义了一个和全局变量text一样的变量,在执行脚本时,首先调用了函数use_local_var_fun,执行结果可以看出,在函数内显示的是局部变量的值,说明局部变量覆盖了全局变量,而在函数执行完成后,在脚本中显示的是全局变量的值,说明局部变量只是在函数内部有效。
脚本 func11.sh 执行结果如下:
6、函数递归
Shell中可以递归调用函数,即函数可直接或间接调用其自身。在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。
递归函数使用不当会无休止的调用自身,为防止敌对调用无终止的进行,必须在函数内有终止递归的条件,常用的办法是加条件判断,满足某种条件后就不再进行递归调用,然后逐层返回。
新建 func12.sh 脚本,内容如下:
#!/bin/bash
# 递归调用函数实现阶乘
fact(){
local num=$1
if [ "$num" -eq 0 ]
then
factorial=1
else
let "decnum=num-1"
# 函数递归调用
fact $decnum
let "factorial=$num * $?"
fi
return $factorial
}
# 脚本调用递归函数
fact $1
echo "Factorial of $1 is $?"
exit 0
执行结果如下: