shell 入门 06 函数
有两种格式可以用来在bash shell脚本中创建函数。第一种格式采用关键字function,后跟
分配给该代码块的函数名。
function name {
commands
}
name() {
commands
}
函数返回值
# 要在脚本中使用函数,只需要像其他shell命令一样,在行中指定函数名就行了
# 每次引用函数名func1时,bash shell会找到func1函数的定义并执行你在那里定义的命令。
# 函数定义不一定非得是shell脚本中首先要做的事,但一定要小心。如果在函数被定义前使用函数,你会收到一条错误消息。
function func1 {
echo "This is an example of a function"
}
func1
# 你也必须注意函数名。记住,函数名必须是唯一的,否则也会有问题。如果你重定义了函数,
# 新定义会覆盖原来函数的定义,这一切不会产生任何错误消息
function func1 {
echo "This is an example of a function"
}
function func1 {
echo "The second function"
}
func1
# bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码
# 有3种不同的方法来为函数生成退出状态码
# 1. 默认退出状态码
# 默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束
# 后,可以用标准变量 $? 来确定函数的退出状态码. 使用函数的默认退出状态码是很危险的
# 因为你无法知道函数中其他命令中是否成功运行
func1() {
echo "trying to display a non-existent file"
ls -l badfile
}
func1
echo "The exit status is: $?"
# 2. 使用 return 命令
# bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个
# 整数值来定义函数的退出状态码,从而提供了一种简单的途径来编程设定函数退出状态码
# 但当用这种方法从函数中返回值时,要小心了。记住下面两条技巧来避免问题
# * 记住,函数一结束就取返回值, 如果在用$?变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失
# * 记住,退出状态码必须是0~255
function dbl {
read -p "Enter a value: " value
echo "doubling the value"
return $(($value * 2))
}
dbl
echo "The new value is $?"
# 3. 使用函数输出
# 正如可以将命令的输出保存到shell变量中一样,你也可以对函数的输出采用同样的处理办
# 法。可以用这种技术来获得任何类型的函数输出,并将其保存到变量中:result='dbl'
function dbl {
read -p "Enter a value: " value
echo $(($value * 2))
}
result=$(dbl)
echo "The new value is $result"
# 这个例子中演示了一个不易察觉的技巧。你会注意到dbl函数实际上输出了两条消息。read
# 命令输出了一条简短的消息来向用户询问输入值。bash shell脚本非常聪明,并不将其作为STDOUT
# 输出的一部分,并且忽略掉它。如果你用echo语句生成这条消息来向用户查询,那么它会与输
# 出值一起被读进shell变量中
# 通过这种技术,你还可以返回浮点值和字符串值。这使它成为一种获取函数返回值的强大方法。
函数传参
# 向函数传递参数
# 由于 bash shell会将函数当作小型脚本来对待。这意味着你可以像普通脚本那样向函数传递参数。
# 函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在 $0
# 变量中定义,函数命令行上的任何参数都会通过$1、$2等定义。也可以用特殊变量$#来判断传给函数的参数数目。
# 在脚本中指定函数时,必须将参数和函数放在同一行
function addem {
if [ $# -eq 0 ] || [ $# -gt 2 ]; then
echo -1
elif [ $# -eq 1 ]; then
echo $(($1 + $1))
else
echo $(($1 + $2))
fi
}
echo -n "Adding 10 and 15: "
value=$(addem 10 15)
echo $value
echo -n "Let's try adding just one number: "
value=$(addem 10)
echo $value
echo -n "Now trying adding no numbers: "
value=$(addem)
echo $value
echo -n "Finally, try adding three numbers: "
value=$(addem 10 15 20)
echo $value
# 由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。下面的例子将会运行失败。
# ./test.sh 2 3
function badfunc1 {
echo $(($1 * $2))
}
if [ $# -eq 2 ]; then
value=$(badfunc1)
echo "The result is $value"
else
echo "Usage: badtest1 a b"
fi
# 尽管函数也使用了$1和$2变量,但它们和脚本主体中的$1和$2变量并不相同。要在函数中使用这些值,必须在调用函数时手动将它们传过去
# ./test.sh 2 3
function badfunc1 {
echo $(($1 * $2))
}
if [ $# -eq 2 ]; then
value=$(badfunc1 $1 $2)
echo "The result is $value"
else
echo "Usage: badtest1 a b"
fi
在函数中处理变量
给shell脚本程序员带来麻烦的原因之一就是变量的作用域。作用域是变量可见的区域。函数
中定义的变量与普通变量的作用域不同。也就是说,对脚本的其他部分而言,它们是隐藏的。
函数使用两种类型的变量:
- 全局变量
- 局部变量
下面几节将会介绍这两种类型的变量在函数中的用法
# 1. 全局变量
# 全局变量是在shell脚本中任何地方都有效的变量。如果你在脚本的主体部分定义了一个全局
# 变量,那么可以在函数内读取它的值。类似地,如果你在函数内定义了一个全局变量,可以在脚
# 本的主体部分读取它的值。默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问。
function dbl {
value=$(($value * 2))
}
read -p "Enter a value: " value
dbl
echo "The new value is: $value"
# 但这其实很危险,尤其是如果你想在不同的shell脚本中使用函数的话。它要求你清清楚楚地
# 知道函数中具体使用了哪些变量,包括那些用来计算非返回值的变量。这里有个例子可说明事情是如何搞砸的。
function func1 {
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
# 2. 局部变量
# 无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这
# 一点,只要在变量声明的前面加上local关键字就可以了
# local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,
# 那么shell将会保持这两个变量的值是分离的。现在你就能很轻松地将函数变量和脚本变量隔离开
# 了,只共享需要共享的变量
function 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
关于局部变量和全局变量心得
val=123
val2=456
function func {
val=234
}
function func2 {
local val2=789
}
# 使用命令替换执行时都不会修改全局变量
$(func)
$(func2)
echo $val # 123
echo $val2 # 456
`func`
`func2`
echo $val # 123
echo $val2 # 456
# 直接执行时会修改全局变量, 但是在加入 local 修饰后也不会修改
func
func2
echo $val # 234
echo $val2 # 456
# 不论哪种调用方法, 函数都无法获取脚本本身参数
# test.sh 1 2 输出
# 0
funcc () {
echo $#
echo $@
}
func
# 间接传入
# test.sh 1 2 输出
# 2
# 1 2
funcc () {
echo $#
echo $@
}
func $@
数组变量和函数
# 1. 向函数传数组参数
# 向脚本函数传递数组变量的方法会有点不好理解。将数组变量当作单个参数传递的话,它不会起作用
# 如果你试图将该数组变量作为函数参数,函数只会取数组变量的第一个值
function testit {
echo "The parameters are: $@"
thisarray=$1
echo "The received array is ${thisarray[*]}"
}
myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
testit $myarray
# 要解决这个问题,你必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使
# 用。在函数内部,可以将所有的参数重新组合成一个新的变量。下面是个具体的例子
# 括号 ($@) 将单值 $@ 重新转成了数组
function testit {
local newarray
newarray=($@)
echo "The new array value is: ${newarray[*]}"
}
myarray=(1 2 3 4 5)
echo "The original array is ${myarray[*]}"
testit ${myarray[*]}
# 从函数返回数组
# ${#var[*]} 将获取数组长度, echo ${var[*]} 将数组所有元素取出并转为了单值,
# 但是在结果赋值中又将其转为了数组, 因此第一个输出只有一个 2, 若没有括号, 则两个输出都是 2 4 6 8 10
function addarray {
local var=($@)
for ((i = 0; i < ${#var[*]}; i++)); do
var[$i]=$((${var[$i]} * 2))
done
echo ${var[*]}
}
arr=(1 2 3 4 5)
result=($(addarray ${arr[*]}))
echo $result
echo ${result[*]}
以下是一些测试后的心得
# 数组
arr=(1 2 3)
# arr 数组所有元素转为单值
a=${arr[*]}
echo ${a[0]} # 1 2 3
echo ${a[1]} # 空
# a 根据单值 $a 重组成数组
a=($a)
echo ${a[0]} # 1
echo ${a[1]} # 2
# 单值
b="1 2 3"
echo ${b[0]} # 1 2 3
echo ${b[1]} # 空
# b 转为数组
b=($b)
echo ${b[0]} # 1
echo ${b[*]} # 1 2 3
function fun {
echo $#
}
# b 此时为数组, $b 等于 ${b[0]}
fun $b # 1
# 数组所有元素转为单值并传入
fun ${b[*]} # 3
# 将单值多项数据作为一个数据传入
fun "${b[*]}" 1 # 2
函数递归
# 5! = 1 * 2 * 3 * 4 * 5 = 120
# 使用递归,方程可以简化成以下形式:
function factorial {
if [ $1 -eq 1 ]; then
echo 1
else
local temp=$(($1 - 1))
local result=$(factorial $temp)
echo $(($result * $1))
fi
}
factorial 5
创建库
bash shell允许创建函数库文件,然后在多个脚本中引用该库文件。
这个过程的第一步是创建一个包含脚本中所需函数的公用库文件。这里有个叫作myfuncs的
库文件,它定义了3个简单的函数
function addem {
echo $(($1 + $2))
}
function multem {
echo $(($1 * $2))
}
function divem {
if [ $2 -ne 0 ]; then
echo $(($1 / $2))
else
echo -1
fi
}
下一步是在用到这些函数的脚本文件中包含myfuncs库文件。从这里开始,事情就变复杂了。
问题出在shell函数的作用域上。和环境变量一样,shell函数仅在定义它的shell会话内有效。
如果你在shell命令行界面的提示符下运行myfuncs shell脚本,shell会创建一个新的shell并在其中
运行这个脚本。它会为那个新shell定义这三个函数,但当你运行另外一个要用到这些函数的脚本
时,它们是无法使用的。这同样适用于脚本。如果你尝试像普通脚本文件那样运行库文件,函数并不会出现在脚本中。
使用函数库的关键在于source命令。source命令会在当前shell上下文中执行命令,而不是
创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库
中的函数了。
source命令有个快捷的别名,称作点操作符(dot operator)。要在shell脚本中运行myfuncs
库文件,只需添加下面这行: . ./myfuncs
# 这个例子假定myfuncs库文件和shell脚本位于同一目录。如果不是,你需要使用相应路径访问该文件
. ./demo.sh
result=$(addem 10 15)
echo "The result is $result"
在命令行上使用函数
可以用脚本函数来执行一些十分复杂的操作。有时也很有必要在命令行界面的提示符下直接
使用这些函数。和在shell脚本中将脚本函数当命令使用一样,在命令行界面中你也可以这样做。这个功能很
不错,因为一旦在shell中定义了函数,你就可以在整个系统中使用它了,无需担心脚本是不是在
PATH环境变量里。重点在于让shell能够识别这些函数。有几种方法可以实现。
# 1. 在命令行上创建函数
# 定义好后就可以直接使用
# 在命令行上创建函数时要特别小心。如果你给函数起了个跟内建命令或另一个命令相同
# 的名字,函数将会覆盖原来的命令。
# 2. 在.bashrc 文件中定义函数
# 在命令行上直接定义shell函数的明显缺点是退出shell时,函数就消失了。对于复杂的函数来,这可是个麻烦事。
# 一个非常简单的方法是将函数定义在一个特定的位置,这个位置在每次启动一个新shell的时候,都会由shell重新载入。
# 最佳地点就是.bashrc文件。bash shell在每次启动时都会在主目录下查找这个文件,不管是交互式shell还是从现有shell中启动的新shell。
# 直接定义函数
# 可以直接在主目录下的.bashrc文件中定义函数。许多Linux发行版已经在.bashrc文件中定义了
# 一些东西,所以注意不要误删了。把你写的函数放在文件末尾就行了。这里有个例子。
function addem {
echo $[ $1 + $2 ]
}
# 该函数会在下次启动新bash shell时生效。随后你就能在系统上任意地方使用这个函数了
# 读取函数文件
# 只要是在shell脚本中,都可以用source命令(或者它的别名点操作符)将库文件中的函数添加到你的.bashrc脚本中
. /home/shino/libraries/myfuncs
# 要确保库文件的路径名正确,以便bash shell能够找到该文件。下次启动shell时,库中的所有
# 函数都可在命令行界面下使用了
# 更好的是,shell还会将定义好的函数传给子shell进程,这样一来,这些函数就自动能够用
# 于该shell会话中的任何shell脚本了。你可以写个脚本,试试在不定义或使用source的情况下,
# 直接使用这些函数. 甚至都不用对库文件使用source,这些函数就可以完美地运行在shell脚本中。