和其他编程语言一样,Shell语言中也存在函数,通过函数可已将实现某一任务的命令进行封装,可以提高程序的可读性和重用性。
一、函数
1.什么是函数
函数就是将一组功能相对独立的代码集中起来形成一个代码块,这个代码块可以实现某个具体的功能。
2.函数的定义
在Shell中有两种定义函数的方法:
function_name ()
{
statement1
statement2
......
}
或者
function function_name ()
{
statement1
statement2
......
}
Shell中函数的命名规则和变量的命名规则基本相同,可以使用任意数字、字母或者下划线,但是只能以字母或者下划线开头。另外用户应该使用具有意义的单词定义函数名,提高代码的可读性。
注意:函数必须在调用之前定义。
#!/usr/bin/env bash
#定义函数
func()
{
echo "Hello World!"
}
#调用函数
func
Output:
$ sh test.sh
Hello World!
3.函数的调用
函数被定义后,就可以调用函数了,调用函数的语法如下:
function_name param1 param2 ......
function_name为调用的函数名称,后面是传入的参数。
#!/usr/bin/env bash
#定义函数
func()
{
echo "Hello World! You Input Number is $@"
}
#调用函数
func 1 2 3 4
Output:
$ sh test.sh
Hello World! You Input Number is 1 2 3 4
注意:调用函数的时候不需要圆括号。
4.函数的链接
函数链接是指在一个函数中调用另外一个函数的过程。
#!/usr/bin/env bash
#定义函数
func()
{
echo "Hello World! You Input Number is $@"
}
main()
{
func 1 2 3 4
echo "I am main function"
}
#调用函数
main
Output:
$ sh test.sh
Hello World! You Input Number is 1 2 3 4
I am main function
注意:调用函数的时候必须满足先定义再调用的顺序。
5.函数的返回值
再Shell中,函数的返回值为return语句来返回某个数值,这个数值的取值范围为0~255。这个return与exit命令返回命令执行的状态码类似,可以通过$?获取函数的返回值。
#!/usr/bin/env bash
#定义函数
length()
{
echo "Hello World"
return 100
}
length
echo "$?"
Output:
$ sh test.sh
Hello World
100
除了用return返回值外,用户可以将需要返回的数据写入到标准输出,然后调用程序中的函数的执行结果赋值给一个变量,这样也可以实现函数返回值的传递。如下,通过echo传递函数返回值:
#!/usr/bin/env bash
#定义函数
length()
{
str=$1
result=0
if [ "$str" != "" ]; then
result=${#str}
fi
echo "$result"
}
len=$(length "hahaha123")
echo "the strings's length is $len"
Output:
$ sh test.sh
the strings's length is 9
6.函数和别名
在Shell中用户可以使用alias命令来设置命令的别名,语法如下:
alias name="command"
其中name是别名,command是要取别名的命令。
函数和别名的类似之处在于,他们都是通过一个名称的映射到一个或者一组命令,无论是函数还是别名,在调用的时候都是执行一个或者一组相关的命令。
7.再议全局变量和局部变量
除了与函数参数相关联的特殊变量外,其他所有变量都是全局有效的。另外函数内部除了使用local关键字修饰的变量,其他变量也是全局变量。
二、函数的参数
1.含有参数的函数调用方法
这个在前面已经用过了,基本语法如下:
function_name arg1 arg2 ....
注意:函数调用的参数用空格隔开。
2.获取函数参数的个数
用户可以通过使用$#来获取函数的参数个数。
#!/usr/bin/env bash
#定义函数
func()
{
echo "Hello World! You Input Number Count is $#"
echo "$@"
}
func 1 2 3 4
Output:
$ sh test.sh
Hello World! You Input Number Count is 4
1 2 3 4
3.通过指定变量接收参数值
与Shell脚本一样,用户可以在函数中通过$后面接数字的方式获取参数值,需要注意的是$0获取的是脚本的名称。
#!/usr/bin/env bash
#定义函数
func()
{
echo "$1 $2 $3 $4"
}
func 1 2 3 4
Output:
$ sh test.sh
1 2 3 4
4.移动位置参数
在Shell脚本中用户可以通过shift命令来使所有参数向左移动一个位置,从而使用户可以使用9以内的位置变量获取超过9个的参数:
#!/usr/bin/env bash
#定义函数
func()
{
while (($# > 0))
do
#只是用$1就可以读取所有变量
echo -n "$1 "
shift
done
}
func 1 2 3 4 5 6 7 8 9 10
Output:
$ sh test.sh
1 2 3 4 5 6 7 8 9 10
注意:shift命令会影响$#的值。
5.通过getopts接收函数参数
getopts是bash内置的一个命令,通过命令,用户可以获取函数的选项以及参数值,或者是脚本的命令行选项以及参数值,语法如下: getopts optstring
在上面的语法中,参数optstring包含一个可以为getopts识别的选项名称列表。如果某个选项名称后面有一个冒号,则表示可以为该选项提供参数值。同时参数值被保存在$OPTARG的系统变量中。getopts命令会遍历每个选项,选项名称被保存在args变量中,这个args可以自己起名字,接在optstring后面即可,下面举个例子:
#!/usr/bin/env bash
function Usage()
{
cat << EOF
test [-h] [-f <filename>] [-v <version>] [-d <descfile>]
EOF
}
function GetOpts()
{
while getopts "hf:v:d:" arg #参数只能由一个字母组成,参数后面接:,表示该参数后面需要接入参数
do
case "$arg" in
h)
Usage
;;
f)
echo "filename: ${OPTARG}"
;;
v)
echo "version: ${OPTARG}"
;;
d)
echo "descfile: ${OPTARG}"
;;
?) #当选项不匹配时,varname的值被设置为?
exit 1
;;
esac
done
}
GetOpts -f test -v v1.0 -d test2
#GetOpts -h
Output:
函数后面接-h
random@random-wz MINGW64 /d/GOCODE/api-test
$ ./test.sh
test [-h] [-f <filename>] [-v <version>] [-d <descfile>]
函数后面接-f test -v v1.0 -d test2
random@random-wz MINGW64 /d/GOCODE/api-test
$ ./test.sh
filename: test
version: v1.0
descfile: test2
6.间接参数传递
在shell中也支持间接参数传递。所谓间接参数传递是指通过间接变量引用,来实现函数参数的传递,如果一个变量的值是另外一个变量的变量名,则该变量称为间接变量:
example:
#定义两个变量,var的值是name变量的名称
var=name
name=random
则用户可以通过下面两种方式来调用name变量:
${name}
${!var}
7.通过全局变量传递数据
参数的作用是在主程序和函数之间传递数据。但是,用户除了可以使用参数传递数据之外,还可以通过全局变量来传递。但是全局变量的作用域是整个程序,包括函数内部。尽管这种方式是有效的,但是在许多编程语言中,这种方式是不被推荐的,因为它会导致程序结果不清晰,代码可读性变差。
8.传递数组参数
在某些情况下,用户可以将一个数组作为参数传递给某个函数,然后函数对数组内容进行相应的处理。但严格来说,Shell并不支持将数组作为参数传递给函数,但用户可以通过一些变通的方式实现数组参数的传递。
首先,用户可以将数组的元素展开,以空格隔开,然后作为多个由空格隔开的参数传递给函数。
#! /usr/bin/env bash
#定义函数
function func()
{
echo "number od elements is $#."
while [ $# -gt 0 ]
do
echo "$1"
shift
done
}
#定义数组
a=(a b "c d" e)
#注意这里用双引号将数组括起来,如果不括起来,则数组中的"c d"将被认为是两个元素,因为a[@]的值为a b c d e
func "${a[@]}"
Output:
$ sh test.sh
number od elements is 4.
a
b
c d
e
三、库函数文件
1.函数库文件的定义
创建一个函数库文件的过程类似于编写一个Shell脚本。脚本和库文件唯一的区别是库文件中通常只包含函数,而脚本中则既可以还包含函数也可以包含执行的代码。由于函数库文件是由主程序载入并执行,因此用户无需拥有库文件的执行权限,只要拥有读取权限即可。
2. 函数库文件的调用
当库文件定义好之后,用户可以通过下面的方式载入库文件:
. filename
其中filename为库文件的名称,圆点和库文件名称之间有一个空格。
举个例子,我们先创建一个库文件test1.sh,内容如下:
#!/usr/bin/env bash
#输出 Hello World
function HelloWorld()
{
echo "Hello World"
}
#计算两个数的和
function Sum()
{
echo $(($1 + $2))
}
在脚本中载入库文件:
#!/usr/bin/env bash
. ./test1.sh
HelloWorld
Sum 1 2
Output:
$ sh test.sh
Hello World
3
四、递归函数
Shell中也支持函数的递归调用,用法和其他编程语言一样,递归函数通过反复的调用本身,直到满足某一个条件退出。这里举一个leetcode上面的题目:
我们通过shell来解答这道题目:
注意:这里还没有想到不使用if语句如何解答本题目,如果你有好的想法,欢迎评论区交流
#!/usr/bin/env bash
RET=1
function sumNums()
{
#定义两个变量,n为输入的值,m为n-1
local n=$1
local m=$(($n - 1))
#如果m小于1则停止递归
if [[ ${m} -lt 1 ]]
then
#当递归函数接收到的参数为1则返回1
RET=1
else
#调用递归函数,传递参数m ,其中m=n-1
sumNums ${m}
#RET为记录函数返回值的参数,他等于递归函数的返回值加递归函数接收的参数
# 比如n=2, 则m=1, RET=2 + (sumNums 1),sumNums 1返回1,所以当n=2时,函数返回2+1=3
RET=$((n + $RET))
fi
}
sumNums $1
echo ${RET}