shell脚本编程笔记(八)—— 脚本函数

函数是一个脚本代码块,可以为其命名并在代码中任何位置重用。要在脚本中使用该代码块时,只要使用所起的函数名就行了(这个过程称为调用函数)。

一、 基本的脚本函数

1. 创建函数

有两种格式可以用来在bash shell脚本中创建函数。函数名必须是唯一的,否则可能会有问题。如果重定义了函数,新定义会覆盖原定义,且不会产生任何错误消息。

  • 第一种格式采用关键字function,后跟分配给该代码块的函数名。
function name {
commands
}
  • 第二种格式更接近于其他编程语言中定义函数的方式,函数名后的()表明正在定义一个函数。
name() {
commands
}

2. 调用函数

要在脚本中使用函数,只需要像其他shell命令一样,在行中指定函数名就行了。

#!/bin/bash
# using a function in a script
function func1 {
echo "This is an example of a function"
}

count=1
while [ $count -le 3 ]
do
func1
count=$[ $count + 1 ]
done

echo "This is the end of the loop"
func1
echo "Now this is the end of the script"
./test1
#输出
This is an example of a function
This is an example of a function
This is an example of a function
This is the end of the loop

This is an example of a function
Now this is the end of the script

 

二、 函数返回值

bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码。有3种不同的方法来为函数生成退出状态码。

1. 默认退出状态码

默认退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用标准变量$?来确定函数的退出状态码查看。

#!/bin/bash
# testing the exit status of a function
func1() {
echo "trying to display a non-existent file"
ls -l badfile
}
echo "testing the function: "
func1
echo "The exit status is: $?"
./test4
#输出
testing the function:
trying to display a non-existent file
ls: badfile: No such file or directory
The exit status is: 1

函数的退出状态码是1,这是因为函数中的最后一条命令没有成功运行,但你无法知道函数中其他命令中是否成功运行。因此,使用函数的默认退出状态码实际是很危险的,即使它返回0也不能代表函数中其他命令执行成功。幸运的是,有几种办法可以解决这个问题。

 

2. 使用 return 命令

bash shell使用return命令来退出函数并返回特定的退出状态码。 return命令允许指定一个整数值来定义函数的退出状态码,提供了一种简单的途径来编程设定函数退出状态码。

#!/bin/bash
# using the return command in a function

function dbl {
read -p "Enter a value: " value
echo "doubling the value"
return $[ $value * 2 ]
}

dbl
echo "The new value is $?"

dbl函数会将$value变量中用户输入的值翻倍,然后用return命令返回结果,再用$?量显示该值。

当用这种方法从函数中返回值时,必须记住下面两条技巧来避免问题:

  • 函数一结束就取返回值。如果查看$?变量前还执行了其他命令,函数的返回值就会丢失,$?变为最近一条命令执行状态。
  • 退出状态码必须是0~255,任何大于255的值都会产生一个错误值。
./test5
# 输出
Enter a value: 200
doubling the value
The new value is 1

那要怎么返回较大的整数值或者其他类型呢?

 

3. 使用函数输出

正如可以将命令的输出保存到shell变量中,你也可以对函数的输出采用同样的处理办法。这种技术可以获得任何类型的函数输出,并将其保存到变量中。

result=$(dbl)   # 这个命令会将dbl函数的输出赋给result变量

下面是在脚本中使用这种方法的例子。

#!/bin/bash
# using the echo to return a value
function dbl {
read -p "Enter a value: " value
echo $[ $value * 2 ]
}

result=$(dbl)
echo "The new value is $result"
./test5b
# 输出
Enter a value: 1000
The new value is 2000

三、 在函数中使用变量

在函数中使用变量时,务必注意它们的定义及处理方式,这是shell脚本中常见错误的根源。

1. 向函数传递参数

函数可以使用标准的参数环境变量来表示调用中传给函数的参数。函数名会在$0中定义,调用中传递的任何参数都会通过$1、$2等定义,也可以用特殊变量$#来判断传
给函数的参数数目。

在脚本中调用函数时,必须将参数和函数放在同一行,像这样:

func1 $value1 10

然后函数可以用参数环境变量来获得参数值。

#!/bin/bash
# passing parameters to a function
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
./test6
# 输出
Adding 10 and 15: 25
Let's try adding just one number: 20
Now trying adding no numbers: -1
Finally, try adding three numbers: -1

由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。下面的例子将会运行失败。

#!/bin/bash
# trying to access script parameters inside a function
function badfunc1 {
echo $[ $1 * $2 ]
}

if [ $# -eq 2 ]
then
value=$(badfunc1)
echo "The result is $value"
else
echo "Usage: badtest1 a b"
fi
./badtest1 10 15
# 输出
./badtest1: * : syntax error: operand expected (error token is "*
")
The result is

尽管函数也使用了$1$2变量,但它们和脚本主体中的$1$2变量并不相同。要在函数中使用这些值,必须在调用函数时手动将它们传过去。

#!/bin/bash
# trying to access script parameters inside a function
function func7 {
echo $[ $1 * $2 ]
}
if [ $# -eq 2 ]
then
value=$(func7 $1 $2)   # 注意这里
echo "The result is $value"
else
echo "Usage: badtest1 a b"
fi
$ ./test7 10 15
#输出
The result is 150

 

2. 在函数中处理变量

shell脚本程序员带来麻烦的原因之一就是变量的作用域。作用域是变量可见的区域,函数中定义的变量与普通变量的作用域不同。也就是说,对脚本的其他部分而言,它们是隐藏的。函数可以使用两种类型的变量:

  • 全局变量
  • 局部变量

1)全局变量

全局变量是在shell脚本中任何地方都有效的变量。如果你在脚本的主体部分定义了一个全局变量,那么可以在函数内读取它的值。类似地,如果你在函数内定义了一个全局变量,可以在脚本的主体部分读取它的值。默认情况下,你在脚本中定义的任何变量都是全局变量。

#!/bin/bash
# using a global variable to pass a value
function dbl {
value=$[ $value * 2 ]
}
read -p "Enter a value: " value
dbl
echo "The new value is: $value"

./test8
#输出
Enter a value: 450
The new value is: 900

$value变量在函数外定义并被赋值。当dbl函数被调用时,该变量及其值在函数中都依然有效。如果变量在函数内被赋予了新值,那么在脚本中引用该变量时,新值也依然有效。但这其实很危险,尤其是如果你想在不同的shell脚本中使用函数的话。它要求你清清楚楚地知道函数中具体使用了哪些变量,包括那些用来计算非返回值的变量。

 

2)局部变量

函数内部使用的任何变量都可以被声明成局部变量,在变量声明的前面加local关键字即可。

local temp

也可以在变量赋值语句中使用local关键字:

local temp=$[ $value + 5 ]

local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,shell将会保持这两个变量的值是分离的。

#!/bin/bash
# demonstrating the local keyword
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
./test9
#输出
The result is 22
temp is smaller

现在,在func1函数中使用$temp变量时,并不会影响在脚本主体中赋给$temp变量的值。

 

四、 数组变量与函数

1. 向函数传数组参数

向脚本函数传递数组变量的方法会有点不好理解。将数组变量当作单个参数传递的话,它不会起作用。必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使用。在函数内部,可以将所有的参数重新组合成一个新的变量。

#!/bin/bash
# array variable to function test
function testit {
local newarray
newarray=(;'echo "$@"')
echo "The new array value is: ${newarray[*]}"
}

myarray=(1 2 3 4 5)
echo "The original array is ${myarray[*]}"
testit ${myarray[*]}
./test10
#输出
The original array is 1 2 3 4 5
The new array value is: 1 2 3 4 5

该脚本用$myarray变量来保存所有的数组元素,然后将它们都放在函数的命令行上。函数随后从命令行参数中重建数组变量。在函数内部,数组仍然可以像其他变量一样使用。

#!/bin/bash
# adding values in an array
function addarray {
local sum=0
local newarray
newarray=($(echo "$@"))
for value in ${newarray[*]}
do
sum=$[ $sum + $value ]
done
echo $sum
}

myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"

arg1=$(echo ${myarray[*]})
result=$(addarray $arg1)
echo "The result is $result"
./test11
#输出
The original array is: 1 2 3 4 5
The result is 15

addarray函数会遍历所有的数组元素,将它们累加在一起。你可以在myarray数组变量中放置任意多的值, addarry函数会将它们都加起来。

 

2. 从函数返回数组

从函数里向shell脚本传回数组变量也用类似的方法。函数用echo语句来按正确顺序输出单个数组值,然后脚本再将它们重新放进一个新的数组变量中。

#!/bin/bash
# returning an array value
function arraydblr {
local origarray
local newarray
local elements
local i
origarray=($(echo "$@"))
newarray=($(echo "$@"))
elements=$[ $# - 1 ]
for (( i = 0; i <= $elements; i++ ))
{
    newarray[$i]=$[ ${origarray[$i]} * 2 ]
}
echo ${newarray[*]}
}

myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"

arg1=$(echo ${myarray[*]})
result=($(arraydblr $arg1))
echo "The new array is: ${result[*]}"
./test12
#输出
The original array is: 1 2 3 4 5
The new array is: 2 4 6 8 10

 

五、 函数递归

局部函数变量的一个特性是自成体系。除了从脚本命令行处获得的变量,自成体系的函数不需要使用任何外部资源,这个特性使得函数可以递归地调用。也就是说,函数可以调用自己来得到结果。通常递归函数都有一个最终可以迭代到的基准值。许多高级数学算法用递归对复杂的方程进行逐级规约,直到基准值定义的那级。

递归算法的经典例子是计算阶乘,一个数的阶乘是该数之前的所有数乘以该数的值,因此 5! = 1 * 2 * 3 * 4 * 5 = 120。

使用递归,可以简化成:x! = x * (x-1)!

#!/bin/bash
# using recursion
function factorial {
if [ $1 -eq 1 ]
then
echo 1
else
local temp=$[ $1 - 1 ]
local result=$(factorial $temp)
echo $[ $result * $1 ]
fi
}

read -p "Enter value: " value
result=$(factorial $value)
echo "The factorial of $value is: $result"
./test13
#输出
Enter value: 5
The factorial of 5 is: 120

 

六、 创建库

使用函数可以在脚本中省去一些输入工作,这一点是显而易见的。但如果你要在多个脚本中使用同一段代码呢?显然,为了使用一次而在每个脚本中都定义同样的函数太过麻烦。有个方法能解决这个问题!bash shell允许创建函数库文件,然后在多个脚本中引用该库文件。

第一步是创建一个包含脚本中所需函数的公用库文件。下面创建一个叫myfuncs库文件,它定义了3个简单的函数。

$ cat myfuncs

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库文件。使用函数库的关键在于source命令,source命令会在当前shell上下文中执行命令,而不是创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库中的函数了。source命令有个快捷的别名. 称作点操作符dot operator)。

要在shell脚本中运行myfuncs库文件,只需添加下面这行。这个例子假定myfuncs库文件和shell脚本位于同一目录。如果不是,你需要使用相应路径访问该文件。

. ./myfuncs

这里有个用myfuncs库文件创建脚本的例子。

$ cat test14
#!/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"
./test14
#输出
The result of adding them is: 15
The result of multiplying them is: 50
The result of dividing them is: 2

该脚本成功地使用了myfuncs库文件中定义的函数。

 

七、 在命令行上使用函数

和在shell脚本中将脚本函数当命令使用一样,在命令行界面中你也可以这样做。

1. 在命令行上创建函数

因为shell会解释用户输入的命令,所以可以在命令行上直接定义一个函数。

有两种定义方法:

  • 采用单行方式定义函数。
$ function divem { echo $[ $1 / $2 ]; }
#调用
$ divem 100 5
20

执行多条命令时,必须记得在每个命令后面加个分号,这样shell才能知道在哪里是命令的起止。

$ function doubleit { read -p "Enter value: " value; echo $[
$value * 2 ]; }
#调用
$ doubleit
Enter value: 20
40
  • 采用多行方式来定义函数。在定义时, bash shell会使用次提示符来提示输入更多命令。用这种方法,你不用在每条命令的末尾放一个分号,只要按下回车键,在函数的尾部使用花括号, shell就会知道你已经完成了函数的定义。
$ function multem {
> echo $[ $1 * $2 ]
> }
#调用
$ multem 2 5
10

 

2. .bashrc 文件中定义函数

在命令行上直接定义shell函数的明显缺点是,退出shell时,函数就消失了。对于复杂的函数来说,这可是个麻烦事。

一个非常简单的方法是将函数定义在一个特定的位置,这个位置在每次启动一个新shell的时候,都会由shell重新载入。最佳地点就是.bashrc文件,bash shell在每次启动时都会在主目录下查找这个文件,不管是交互式shell还是从现有shell中启动的新shell。

 

1) 直接定义函数

可以直接在主目录下的.bashrc文件中定义函数。许多Linux发行版已经在.bashrc文件中定义了一些东西,注意不要误删了,把你写的函数放在文件末尾就行。

$ cat .bashrc
# .bashrc
if [ -r /etc/bashrc ]; then
. /etc/bashrc
fi
#新增
function addem {
echo $[ $1 + $2 ]
}

该函数会在下次启动新bash shell时生效。随后你就能在系统上任意地方使用这个函数了。

2) 读取函数文件

只要是在shell脚本中,都可以用source命令(或者它的别名.)将库文件中的函数添加到你的.bashrc脚本中。

$ cat .bashrc
# .bashrc
if [ -r /etc/bashrc ]; then
. /etc/bashrc
fi
. /home/rich/libraries/myfuncs  #注意这个

要确保库文件的路径名正确,以便bash shell能够找到该文件。下次启动shell时,库中的所有函数都可在命令行界面下使用了。

$ addem 10 5
15
$ multem 10 5
50
$ divem 10 5
2

更好的是,shell还会将定义好的函数传给子shell进程,这样一来,这些函数就自动能够用于该shell会话中的任何shell脚本了。甚至都不用对库文件使用source,这些函数就可以完美地运行在shell脚本中。

#!/bin/bash
# using a function defined in the .bashrc file
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"
./test15
#输出
The result of adding them is: 15
The result of multiplying them is: 50
The result of dividing them is: 2

猜你喜欢

转载自blog.csdn.net/Hehuyi_In/article/details/111058064
今日推荐