欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-10 12:16:10
...

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

一、 基本的脚本函数

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 "[email protected]"')
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 "[email protected]"))
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 "[email protected]"))
newarray=($(echo "[email protected]"))
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
相关标签: 脚本&命令 shell