Shell函数
编写shell脚本时,经常需要在多个地方使用了同一段代码。如果只是一小段代码,一般也无关紧要。但要在shell脚本中多次重写大块代码段就太累人了。bash shell提供的用户自定义函数功能可以解决这个问题。可以将shell脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次使用。除了代码重用,在shell中使用函数可以有以下优点:
- 代码重用:在脚本的不同部分多次调用,避免了代码的冗余。
- 模块化:将复杂脚本分解成多个独立模块,提高代码的可维护性和可读性。
- 可测试性:便于对各个功能编写单元测试,提高脚本质量。
函数是一个脚本代码块,你可以为其命名并在代码中任何位置重用。要在脚本中使用该代码块时,只要使用所起的函数名就行了(这个过程称为函数调用)。
基本脚本函数
创建函数、调用函数
第一种方式采用function关键字,后面跟函数名以及代码块
function name { commands }name属性定义了赋予函数的唯一名称。脚本中定义的每个函数都必须有一个唯一的名称。commands是构成函数的一条或多条bash shell命令。在调用
该函数时,bash shell会按命令在函数中出现的顺序依次执行,就像在普通脚本中一样。第二种方式更接近其他编程语言的定义方式
name() { commands }
#!/bin/bash
# 演示函数定义与调用
# 注意:直接在这里调用 greet 会报错,因为函数尚未定义
# greet
greet() {
echo "[提示] 你好,欢迎学习 Shell 函数!"
}
# 正确的调用方式:在定义之后调用
greet
- 当函数在被定义之前使用的话会产生错误信息
#!/bin/bash
# 演示函数在脚本中的位置以及调用顺序
count=1
echo "[提示] 这一行在函数定义之前执行"
func1() {
echo "[函数1] 这是一个函数示例,正在执行第 $count 次循环"
}
while [ $count -le 3 ]
do
func1
count=$((count + 1))
done
echo "[完成] 循环运行结束"
# 下面演示如果调用未定义的函数会发生什么
# func2
echo "[完成] 脚本运行结束"
func2() {
echo "[函数2] 脚本末尾定义的函数"
}
作业1:修改以下代码,通过函数实现模拟掷骰子的功能。
#!/bin/bash
sides=6
result=$((RANDOM % sides + 1))
echo "You rolled a $result!"
返回值
bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码。有3种不同的方法来为函数生成退出状态码。
默认退出状态码
默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用特殊变量$?来确定函数的退出状态码。
#!/bin/bash
# 演示函数的默认退出状态码
check_file() {
# 模拟一个失败的命令
ls -l badfile 2>/dev/null
echo "[函数运行中] 已尝试列出文件"
}
check_file
# $? 总是返回函数中最后一条执行完毕的命令的退出状态码
echo "[结果] 函数的退出状态码为: $?"
如果注释掉函数末尾的 echo 语句,由于前面的 ls 命令执行失败,该函数的退出状态码将是非零值。反之,若最后一条语句执行成功,状态码则为 0。
使用return命令
bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个整数值来定义函数的退出状态码
#!/bin/bash
# 演示使用 return 命令返回结果
dbl() {
read -p "[交互] 请输入一个数值: " value
echo "[计算中] 正在执行翻倍运算..."
return $((value * 2))
}
dbl
# 注意:$? 的取值范围是 0-255,超过 255 会产生溢出(结果不对)
echo "[结果] 函数通过 return 返回的值等于 $?"
运行脚本对比:
ubuntu@sh:~$ bash test.sh
[交互] 请输入一个数值: 100
[计算中] 正在执行翻倍运算...
[结果] 函数通过 return 返回的值等于 200
# 演示溢出:200 * 2 = 400 (400 - 256 = 144)
ubuntu@sh:~$ bash test.sh
[交互] 请输入一个数值: 200
[计算中] 正在执行翻倍运算...
[结果] 函数通过 return 返回的值等于 144
使用函数输出
正如可以将命令的输出保存到shell变量中一样,也可以对函数的输出采用同样的处理办法。可以用这个方法来获得任何类型的函数输出,并将其保存到变量中:
result=`dbl`
这个命令会将dbl函数的输出赋给$result变量。
#!/bin/bash
# 演示通过 echo 输出将函数结果保存到变量中
calc_double() {
read -p "[交互] 请输入需要翻倍的数值: " value
# echo 的输出可以被命令替换 $(...) 捕获
echo $((value * 2))
}
# 调用函数并将输出结果赋给变量 result
result=$(calc_double)
echo "[结果] 函数计算得到的翻倍值为:$result"
ubuntu@sh:~$ bash test.sh
[交互] 请输入需要翻倍的数值: 200
[结果] 函数计算得到的翻倍值为:400
作业2:编写以上脚本使用命令替换保存函数输出。
函数中使用变量
向函数传递参数
bash shell会将函数当作小型脚本来对待。这意味着可以像普通脚本那样向函数传递参数。
函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在$0变量中定义,函数命令行上的任何参数都会通过$1、$2等定义。也可以用特殊变量$#来判断传给函数的参数数目。
#!/bin/bash
# 定义一个函数,接收两个参数并输出它们
print_two_args() {
echo "[参数] 第一个参数是:$1"
echo "[参数] 第二个参数是:$2"
echo "[状态] 参数总量:$#"
}
# 调用函数并传递两个参数
print_two_args "参数1" "参数2"
}
调用函数,并传递两个参数
print_two_args “hello” “world”
```shell
第一个参数是:hello
第二个参数是:world
在函数中处理变量
作用域是变量可见的区域。函数中定义的变量与普通变量的作用域不同。也就是说,对脚本的其他部分而言,它们是隐藏的。
全局变量
对于全局变量,可以直接在函数中使用或修改。例如:
#!/bin/bash
# 定义全局变量
name="Tom"
# 定义函数
my_func() {
echo "Hello, $name"
name="Jerry"
}
# 调用函数
my_func
echo "Name is $name" # 输出 "Name is Jerry"
局部变量
#!/bin/bash
# 定义函数
my_func() {
# 定义局部变量
local count=0
# 修改局部变量
count=$((count + 1))
# 输出局部变量
echo "Count is $count"
}
# 调用函数
my_func # 输出 "Count is 1"
需要注意的是,在函数中使用局部变量时,变量名前必须加上 **local**关键字,否则会被认为是全局变量。如果函数中定义了一个与全局变量同名的局部变量,则在函数中访问该变量时,会优先使用局部变量。
#!/bin/bash
# 在函数中使用局部变量不会影响到脚本主体中的全局变量的值
func1() {
local temp=$((value + 5))
result=$((temp * 2))
}
temp=4
value=6
func1
echo "The result is $result"
if [ $temp -gt $value ]
then
echo "temp is larger"
else
echo "temp is smaller"
fi
作业1:编写以上脚本,掌握函数内局部变量的定义与作用范围。
在命令行上使用函数
可以用脚本函数来执行一些十分复杂的操作。有时也很有必要在命令行界面的提示符下直接使用这些函数。一旦在shell中定义了函数,你就可以在整个系统中使用它了,无需担心脚本是不是在PATH环境变量里。重点在于让shell能够识别这些函数。
在命令行上创建函数
单行方式定义函数
ubuntu@sh:~$ addem() { echo $(($1 + $2)); } ubuntu@sh:~$ addem 1 2 3多行方式定义函数
ubuntu@sh:~$ multem() { > echo $(($1 * $2)) > } ubuntu@sh:~$ multem 2 4 8 ubuntu@sh:~$在函数的尾部使用花括号,shell就会知道你已经完成了函数的定义。
在.bashrc文件中定义函数
在命令行上直接定义shell函数的明显缺点是退出shell时,函数就消失了,这对于一些复杂函数并不适用。此时可以将函数写入一个特定位置并且在shell启动时由shell重新载入。.bashrc文件会在每次shell启动时在主目录下被查找并调用,因此可以在主目录下的.bashrc文件中定义函数。
将以下内容写入到.bashrc文件的末尾:
addem() {
echo $(($1 + $2))
}
创建库
通过创建函数库文件可以方便在多个脚本中使用同一段代码。以下代码保存在myfuncs文件中:
addem() {
echo $(($1 + $2))
}
multem() {
echo $(($1 * $2))
}
divem() {
if [ "$2" -ne 0 ]
then
echo $(($1 / $2))
else
echo -1
fi
}
在另一个脚本中执行myfuncs
#!/bin/bash
# using a library file the wrong way
./myfuncs
result=$(addem 10 15)
echo "The result is $result"
输出信息
addem: command not found
The result is
使用函数库的关键在于source命令。source命令会在当前shell上下文中执行命令,而不是创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库中的函数了。
#!/bin/bash
# using functions defined in a library file
. ./myfuncs
value1=10
value2=5
result1=$(addem $value1 $value2)
result2=$(multem $value1 $value2)
result3=$(divem $value1 $value2)
echo "The result of adding them is: $result1"
echo "The result of multiplying them is: $result2"
echo "The result of dividing them is: $result3"
作业2:编写以上脚本掌握库文件的使用