shell脚本中的函数使用
文章目录
基本的脚本函数
函数是一个脚本代码块,你可以为其命名并在代码中任何位置重 用。要在脚本中使用该代码块时,只要使用所起的函数名就行了(这个过程称为调用函数)。
创建函数
有两种格式可以用来在bash shell脚本中创建函数。第一种格式采用关键字function,后跟 分配给该代码块的函数名。
function name {
commands
}
name属性定义了赋予函数的唯一名称。脚本中定义的每个函数都必须有一个唯一的名称。
commands是构成函数的一条或多条bash shell命令。在调用该函数时,bash shell会按命令在 函数中出现的顺序依次执行,就像在普通脚本中一样。
在bash shell脚本中定义函数的第二种格式更接近于其他编程语言中定义函数的方式。
name()
{
commands
}
函数名后的空括号表明正在定义的是一个函数。这种格式的命名规则和之前定义shell脚本函数的格式一样。
使用函数
要在脚本中使用函数,只需要像其他shell命令一样,在行中指定函数名就行了。
function func1 {
echo "This is an example of a function"
}
count=1
while [ $count -le 5 ]
do
func1
count=$[ $count + 1 ]
done
记住,函数名必须是唯一的,否则也会有问题。如果你重定义了函数, 新定义会覆盖原来函数的定义,这一切不会产生任何错误消息。
返回值
bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码
默认退出状态码
默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码.但你无法知道函数中其他命令中是否成功运行。在函数执行结束后,可以用标准变量$?来确定函数的退出状态码.
使用return命令
bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个整数值来定义函数的退出状态码,从而提供了一种简单的途径来编程设定函数退出状态码。
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。
如果在用$?变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失。记住,$? 变量会返回执行的最后一条命令的退出状态码。
要返回较大的整数值或者字符串值的话,你就不能用这种返回值的方法了。
使用函数输出
正如可以将命令的输出保存到shell变量中一样,你也可以对函数的输出采用同样的处理办法。可以用这种技术来获得任何类型的函数输出,并将其保存到变量中:
function dbl {
read -p "Enter a value: " value
echo $[ $value * 2 ]
result='dbl'
这个命令会将dbl函数的输出赋给$result变量。新函数会用echo语句来显示计算的结果。该脚本会获取dbl函数的输出,而不是查看退出状 态码。
read命令输出了一条简短的消息来向用户询问输入值。bash shell脚本非常聪明,并不将其作为STDOUT 输出的一部分,并且忽略掉它。如果你用echo语句生成这条消息来向用户查询,那么它会与输 出值一起被读进shell变量中。
在函数中使用变量
向函数传递参数
bash shell会将函数当作小型脚本来对待。这意味着你可以像普通脚本那样向函数传递参数.函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。
例如,函数名会在$0 变量中定义,函数命令行上的任何参数都会通过$1、$2等定义。也可以用特殊变量$#来判断传 给函数的参数数目。
在脚本中指定函数时,必须将参数和函数放在同一行,像这样:
func1 $value1 10
然后函数可以用参数环境变量来获得参数值。**函数无法直接获取脚本在命令行中的参数值。**这里有个使用此方法向函数传值的例子。
function addem {
if [ $# -eq 0 ] || [ $# -gt 2 ]
then
echo -1
elif [ $# -eq 1 ]
then
echo $[ $1 + $1 ]
else
echo $[ $1 + $2 ]
fi
}
value=$(addem 10 15)
在函数中处理变量
给shell脚本程序员带来麻烦的原因之一就是变量的作用域。作用域是变量可见的区域。函数 中定义的变量与普通变量的作用域不同。也就是说,对脚本的其他部分而言,它们是隐藏的。
函数使用两种类型的变量:
- 全局变量
- 局部变量
1. 全局变量
全局变量是在shell脚本中任何地方都有效的变量。如果你在脚本的主体部分定义了一个全局 变量,那么可以在函数内读取它的值。类似地,如果你在函数内定义了一个全局变量,可以在脚 本的主体部分读取它的值。
默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问。
$ cat test8
#!/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函数被调用时,该变量及其值在函数中都依然有 效。如果变量在函数内被赋予了新值,那么在脚本中引用该变量时,新值也依然有效。
2. 局部变量
无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这 一点,只要在变量声明的前面加上local关键字就可以了。
local temp
local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量, 那么shell将会保持这两个变量的值是分离的。现在你就能很轻松地将函数变量和脚本变量隔离开 了,只共享需要共享的变量。
数组变量和函数
向函数传数组参数
将数组变量当作单个参数传递的话,它不会起作用。
$ cat badtest3
#!/bin/bash
# trying to pass an array variable
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
如果你试图将该数组变量作为函数参数,函数只会取数组变量的第一个值。
要解决这个问题,你必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使 用。在函数内部,可以将所有的参数重新组合成一个新的变量。
testit ${myarray[*]}
用$myarray变量来保存所有的数组元素,然后将它们都放在函数的命令行上。该函 数随后从命令行参数中重建数组变量。在函数内部,数组仍然可以像其他数组一样使用。
从函数返回数组
从函数里向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[*]}"
$
该脚本用$arg1变量将数组值传给arraydblr函数。
arraydblr函数将该数组重组到新的数 组变量中,生成该输出数组变量的一个副本。然后对数据元素进行遍历,将每个元素值翻倍并 将结果存入函数中该数组变量的副本。
函数递归
局部函数变量的一个特性是自成体系。除了从脚本命令行处获得的变量,自成体系的函数不 需要使用任何外部资源。
这个特性使得函数可以递归地调用,也就是说,函数可以调用自己来得到结果。通常递归函 数都有一个最终可以迭代到的基准值。许多高级数学算法用递归对复杂的方程进行逐级规约,直到基准值定义的那级。
递归算法的经典例子是计算阶乘。一个数的阶乘是该数之前的所有数乘以该数的值。
function factorial {
if [ $1 -eq 1 ]
then
echo 1
else
local temp=$[ $1 - 1 ]
local result='factorial $temp'
echo $[ $result * $1 ]
fi
}
创建库
使用函数可以在脚本中省去一些输入工作,这一点是显而易见的。但如果你碰巧要在多个脚 本中使用同一段代码呢?显然,为了使用一次而在每个脚本中都定义同样的函数太过麻烦。
有个方法能解决这个问题!bash shell允许创建函数库文件,然后在多个脚本中引用该库文件。
这个过程的第一步是创建一个包含脚本中所需函数的公用库文件。这里有个叫作myfuncs的 库文件.
下一步是在用到这些函数的脚本文件中包含myfuncs库文件。从这里开始,事情就变复杂了。
问题出在shell函数的作用域上。和环境变量一样,shell函数仅在定义它的shell会话内有效。 如果你在shell命令行界面的提示符下运行myfuncs shell脚本,shell会创建一个新的shell并在其中 运行这个脚本。它会为那个新shell定义这三个函数,但当你运行另外一个要用到这些函数的脚本 时,它们是无法使用的。
这同样适用于脚本。如果你尝试像普通脚本文件那样运行库文件,函数并不会出现在脚本中。
$ cat badtest4
#!/bin/bash
# using a library file the wrong way
./myfuncs
result=$(addem 10 15)
echo "The result is $result"
$
$ ./badtest4
./badtest4: addem: command not found
The result is
$
使用函数库的关键在于source命令。source命令会在当前shell上下文中执行命令,而不是 创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库 中的函数了。
source命令有个快捷的别名,称作点操作符(dot operator)。要在shell脚本中运行myfuncs 库文件,只需添加下面这行:
. ./myfuncs
这个例子假定myfuncs库文件和shell脚本位于同一目录。如果不是,你需要使用相应路径访问该文件。
在命令行上使用函数
和在shell脚本中将脚本函数当命令使用一样,在命令行界面中你也可以这样做。这个功能很不错,因为一旦在shell中定义了函数,你就可以在整个系统中使用它了,无需担心脚本是不是在PATH环境变量里。重点在于让shell能够识别这些函数。
在命令行上创建函数
因为shell会解释用户输入的命令,所以可以在命令行上直接定义一个函数。有两种方法。
一种方法是采用单行方式定义函数。
$functiondivem{echo$[$1/$2]; }
$ divem 100 5
20
$
当在命令行上定义函数时,你必须记得在每个命令后面加个分号,这样shell就能知道在哪里 是命令的起止了。
另一种方法是采用多行方式来定义函数。在定义时,bash shell会使用次提示符来提示输入更 多命令。用这种方法,你不用在每条命令的末尾放一个分号,只要按下回车键就行。
$ function multem {
> echo $[ $1 * $2 ]
>}
$ multem 2 5
10
$
在函数的尾部使用花括号,shell就会知道你已经完成了函数的定义。
在命令行上创建函数时要特别小心。如果你给函数起了个跟内建命令或另一个命令相同的名字,函数将会覆盖原来的命令。
在.bashrc文件中定义函数
在命令行上直接定义shell函数的明显缺点是退出shell时,函数就消失了。对于复杂的函数来 说,这可是个麻烦事。
一个非常简单的方法是将函数定义在一个特定的位置,这个位置在每次启动一个新shell的时 候,都会由shell重新载入。
最佳地点就是.bashrc文件。bash shell在每次启动时都会在主目录下查找这个文件,不管是交互式shell还是从现有shell中启动的新shell。
1. 直接定义函数
可以直接在主目录下的.bashrc文件中定义函数。把你写的函数放在文件末尾就行了。
该函数会在下次启动新bash shell时生效。随后你就能在系统上任意地方使用这个函数了。
2. 读取函数文件
只要是在shell脚本中,都可以用source命令(或者它的别名点操作符)将库文件中的函数添加到你的.bashrc脚本中。
$ cat .bashrc
# .bashrc
# Source global definitions
if [ -r /etc/bashrc ]; then
. /etc/bashrc
fi
. /home/rich/libraries/myfuncs
$
要确保库文件的路径名正确,以便bash shell能够找到该文件。下次启动shell时,库中的所有 函数都可在命令行界面下使用了。
更好的是,shell还会将定义好的函数传给子shell进程,这样一来,这些函数就自动能够用于该shell会话中的任何shell脚本了。甚至都不用对库文件使用source,这些函数就可以完美地运行在shell脚本中。
文章作者 Forz
上次更新 2017-08-11