基本的脚本函数
函数是一个脚本代码块,用户可以为其命名并在代码中任何位置重用。要在脚本中使用该代码块时,只要使用所起的函数名就行了(这个过程称为调用函数)。
创建函数
创建函数有2种方式:第一种,使用关键字function
,如下所示:
|
|
name属性定义了赋予函数的唯一名称。脚本中定义的每个函数都必须有一个唯一的名称。commands是构成函数的一条或多条bash shell命令。在调用该函数时,bash shell会按命令在函数中出现的顺序依次执行,就像在普通脚本中一样。
第二种构建函数的格式如下:
|
|
函数名后的空括号表明正在定义的是一个函数。这种格式的命名规则和之前定义shell脚本函数的格式一样。
使用函数
要在脚本中使用函数,只需要像其他shell命令一样,在行中指定函数名就行了。
|
|
运行结果如下所示:
|
|
每次引用函数名func1时,bash shell会找到func1函数的定义并执行你在那里定义的命令。如果在函数被定义前使用函数,会收到一条错误消息,如下所示:
|
|
运行结果如下所示:
|
|
第一个函数func1的定义出现在脚本中的几条语句之后,运行起来没问题。当func1函数 在脚本中被使用时,shell知道去哪里找它。 脚本试图在func2函数被定义之前使用它。由于func2函数还没有定义,脚本运行函数调用处时,产生了一条错误消息。 此外,还需要注意函数名,函数名必须是唯一的,否则也会有问题。如果你重定义了函数,新定义会覆盖原来函数的定义,这一切不会产生任何错误消息,如下所示:
|
|
运行结果如下所示:
|
|
func1函数最初的定义工作正常,但重新定义该函数后,后续的函数调用都会使用第二个定义。
返回值
bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码。有3种不同的方法来为函数生成退出状态码。
默认退出状态码
默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用标准变量$?来确定函数的退出状态码。
|
|
运行结果如下所示:
|
|
函数的退出状态码是2,这是因为函数中的最后一条命令没有成功运行。但你无法知道函数中其他命令中是否成功运行。看下面的例子。
|
|
运行结果如下所示:
|
|
在这个案例中,由于函数最后一条语句echo运行成功,该函数的退出状态码就是0,尽管其中有一条 命令并没有正常运行。使用函数的默认退出状态码是很危险的。在bash shell中,有几种办法可以解决这个问题。
使用return命令
bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个 整数值来定义函数的退出状态码,从而提供了一种简单的途径来编程设定函数退出状态码。
|
|
运行结果如下所示:
|
|
dbl函数会将$value
变量中用户输入的值翻倍,然后用return命令返回结果。脚本用$?
变量显示了该值。但当用这种方法从函数中返回值时,需要注意两点:第一,函数一结束就取返回值;第二,退出状态码必须是0~255。如果在用$?
变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失。$?
变量会返回执行的最后一条命令的退出状态码。第二个问题界定了返回值的取值范围。由于退出状态码必须小于256,函数的结果必须生成一个小于256的整数值。任何大于256的值都会产生一个错误值,如下所示:
|
|
使用函数的输出
将函数的份输出保存到shell变量中,就能获取任何类型的函数输出,例如result='dbl
就是将dbl函数的输出赋值给$result
,如下所示:
|
|
运行结果如下所示:
|
|
新函数会用echo语句来显示计算的结果。该脚本会获取dbl函数的输出,而不是查看退出状态码。在这个例子中,dbl函数实际上输出了两条消息。read命令输出了一条简短的消息来向用户询问输入值。bash shell脚本并不将其作为STDOUT输出的一部分,并且忽略掉它。如果你用echo语句生成这条消息来向用户查询,那么它会与输出值一起被读进shell变量中。通过这种技术,还可以返回浮点值和字符串值。
在函数中使用变量
在test5例子的脚本里,在函数里用了一个叫作$value的变量来保存处理后的值。在函数中使用变量时,用户需要注意它们的定义方式以及处理方式。这是shell脚本中常见错误的根源。
向函数传递参数
函数使用两种类型的变量,分别为全局变量和局部变量。
全局变量
全局变量是在shell脚本中任何地方都有效的变量。如果在脚本的主体部分定义了一个全局 变量,那么可以在函数内读取它的值。同样的,如果你在函数内定义了一个全局变量,可以在脚本的主体部分读取它的值。 默认情况下,在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问,如下所示:
|
|
运行结果如下所示:
|
|
$value
变量在函数外定义并被赋值。当dbl函数被调用时,该变量及其值在函数中都依然有效。如果变量在函数内被赋予了新值,那么在脚本中引用该变量时,新值也依然有效。但这操作其实很危险,因为如果是不同的脚本都使用该函数,有可能造成冲突,如下所示:
|
|
运行结果如下所示:
|
|
由于函数中用到了$temp
变量,它的值在脚本中使用时受到了影响,产生了意想不到的后果。
局部变量
在函数内部,通常无需使用全局变量,使用局部变量即可,要实现这一点,只要在变量声明的前面加上 local 关键字就可以了,例如local temp
,local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量, 那么shell将会保持这两个变量的值是分离的。现在你就能很轻松地将函数变量和脚本变量隔离开了,只共享需要共享的变量。如下所示:
|
运行结果如下所示:
|
|
在 func1 函数中使用$temp
变量时,并不会影响在脚本主体中赋给 $temp
变量的值。
数组变量和函数
向函数传数组参数
如果你试图将该数组变量作为函数参数,函数只会取数组变量的第一个值,如下所示:
|
|
结果运行如下:
|
|
要解决上述问题,用户必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使用。在函数内部,可以将所有的参数重新组合成一个新的变量,如下所示:
|
|
运行结果如下所示:
|
|
该脚本用$myarray
变量来保存所有的数组元素,然后将它们都放在函数的命令行上。该函数随后从命令行参数中重建数组变量。在函数内部,数组仍然可以像其他数组一样使用,如下所示:
|
|
运行结果如下所示:
|
|
addarray函数会遍历所有的数组元素,将它们累加在一起。你可以在myarray数组变量中放置任意多的值,addarry函数会将它们都加起来。
从函数返回数组
从函数里向shell脚本传回数组变量也用类似的方法。函数用echo语句来按正确顺序输出单个数组值,然后脚本再将它们重新放进一个新的数组变量中。
|
|
运行结果如下所示:
|
|
该脚本用$arg1
变量将数组值传给arraydblr函数。arraydblr函数将该数组重组到新的数 组变量中,生成该输出数组变量的一个副本。然后对数据元素进行遍历,将每个元素值翻倍,并 将结果存入函数中该数组变量的副本。 arraydblr函数使用echo语句来输出每个数组元素的值。脚本用arraydblr函数的输出来 重新生成一个新的数组变量。
函数递归
函数可以调用函数自身,这个过程就称为函数的递归。下面看一个函数,这个函数就是通过递归来计算阶乘,如下所示:
|
|
运行结果如下所示:
|
|
在创建了函数后,可以在其他的脚本中调用。
创建库
如果用户要在多个脚本中使用同一段代码的话,这就需要创建函数库文件,然后在多个脚本中引用该库文件。这个过程的第一步是创建一个包含脚本中所需函数的公用库文件,如下所示:
|
|
下一步是在用到这些函数的脚本文件中包含myfuncs库文件。从这里开始,事情就变复杂了。问题出在shell函数的作用域上。和环境变量一样,shell函数仅在定义它的shell会话内有效。如果你在shell命令行界面的提示符下运行myfuncs shell脚本,shell会创建一个新的shell并在其中运行这个脚本。它会为那个新shell定义这三个函数,但当你运行另外一个要用到这些函数的脚本时,它们是无法使用的。这同样适用于脚本。如果你尝试像普通脚本文件那样运行库文件,函数并不会出现在脚本中,如下所示:
|
|
运行后如下所示:
|
|
使用函数库的需要用到source
命令。source命令会在当前shell上下文中执行命令,而不是创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库中的函数了。source命令有个快捷的别名,称作点操作符(dotoperator)
。要在shell脚本中运行myfuncs库文件,只需添加这一行,即. ./myfuncs
(两个点之间有空格),这个例子是假定myfuncs库文件与shell脚本位于同一目录,如果不是,则需要输入全路径。使用source命令的脚本如下所示:
|
|
运行结果如下所示:
|
|
在命令行上使用函数
在命令行界面中也可以使用函数,一旦在shell中定义了函数,用户就可以在整个系统中使用它了,无需担心脚本是不是在PATH环境变量里。
在命令行上创建函数
第1种方法:直接定义函数
这一种方法使用的是单行试,需要函数主体部分的每个命令后面加上分号,如下所示:
|
|
再看一个案例:
|
|
第2种方法:多行形式
还可以采用多行方式定义函数,此种情况下,bash shell会用提示符提示输入更多的命令,此种方法不需要在命令后面加分号,直接回车就行,如下所示:
|
|
在.bashrc
文件中定义函数
在命令行上直接定义shell函数的明显缺点是退出shell时,函数就消失了。对于复杂的函数来说,这种形式并不常用。解决这个问题的方式就是将函数定义在一个特定的位置,这个位置在每次启动一个新shell的时候,都会由shell重新载入。最佳地点就是.bashrc文件。bash shell在每次启动时都会在主目录下查找这个文件,不管是交互式shell还是从现有shell中启动的新shell。
直接定义函数
打开.bashrc
文件,下拉在文件的末尾处写入函数即可,如下所示:
|
|
运行结果如下所示:
|
|
也可以将库文件写入到.bashrc
文件中,如下所示:
|
|
然后source ~/.bashrc,
这样在shell中就可以使用myfuncs.sh中的函数了,如下所示:
|
|
案例
shtool库提供了一些简单的shell脚本函数,可以用来完成日常的shell功能,例如处理临时文件和目录或者格式化输出显示。
下载及安装
|
|
构建库
shtool文件必须针对特定的Linux环境进行配置。配置工作必须使用标准的configure和make命令,这两个命令常用于C编程环境。要构建库文件,输入以下命令:
|
|
configure命令会检查构建shtool库文件所必需的软件。一旦发现了所需的工具,它会使用工具路径修改配置文件。make命令负责构建shtool库文件。最终的结果(shtool)是一个完整的库软件包。也可以使用make命令测试这个库文件。
|
|
测试模式会测试shtool库中所有的函数。如果全部通过测试,就可以将库安装到Linux系统中的公用位置,这样所有的脚本就都能够使用这个库了。要完成安装,需要使用make命令的install选项。不过需要以root用户的身份运行该命令。
|
|
现在就能在自己的shell脚本中使用这些函数了。
shtool 库函数
shtool库提供了大量方便的、可用于shell脚本的函数。如下所示:
函数 | 描述 |
---|---|
Arx | 创建归档文件(包含一些扩展功能) |
Echo | 显示字符串,并提供了一些扩展构件 |
fixperm | 改变目录树中的文件权限 |
install | 安装脚本或文件 |
mdate | 显示文件或目录的修改时间 |
mkdir | 创建一个或更多目录 |
Mkln | 使用相对路径创建链接 |
mkshadow | 创建一棵阴影树 |
move | 带有替换功能的文件移动 |
Path | 处理程序路径 |
platform | 显示平台标识 |
Prop | 显示一个带有动画效果的进度条 |
rotate | 转置日志文件 |
Scpp | 共享的C预处理器 |
Slo | 根据库的类别,分离链接器选项 |
Subst | 使用sed的替换操作 |
Table | 以表格的形式显示由字段分隔(field |
tarball | 从文件和目录中创建tar文件 |
version | 创建版本信息文件 |
shtool函数的使用格式为shtool [options] [function [options] [args]]
。
使用库
下面是一个在shell脚本中使用platform函数的例子,如下所示:
|
|
运行后如下所示:
|
|
platform函数会返回Linux发行版以及系统所使用的CPU硬件的相关信息。还有一个函数是prop函数。它可以使用\、|、/和-字符创建一个旋转的进度条。可以告诉shell脚本用户目前正在进行一些后台处理工作。要使用prop函数,只需要将希望监看的输出管接到shtool脚本就行了,如下所示:
|
|
prop函数会在处理过程中不停地变换进度条字符。在本例中,输出信息来自于ls命令。你能看到多少进度条取决于CPU能以多快的速度列出/usr/bin中的文件,-p选项允许你定制输出文本,这段文本会出现在进度条字符之前。