下载
第19章 shell 函 数
本书目前为止所有脚本都是从头到尾执行。这样做很好,但你也许已经注意到有些脚本
段间互相重复。
shell允许将一组命令集或语句形成一个可用块,这些块称为 shell函数。
本章内容有:
定义函数。
在脚本中使用函数。
在函数文件中使用函数。
函数举例。
函数由两部分组成:
函数标题。
函数体。
标题是函数名。函数体是函数内的命令集合。标题名应该唯一;如果不是,将会混淆结
果,因为脚本在查看调用脚本前将首先搜索函数调用相应的 shell。
定义函数的格式为:
函数名()
{
命令1
...
}
或者
函数名(){
命令1
...
}
两者方式都可行。如果愿意,可在函数名前加上关键字 function,这取决于使用者。
function 函数名()
{ ...
}
可以将函数看作是脚本中的一段代码,但是有一个主要区别。执行函数时,它保留当前
shell和内存信息。此外如果执行或调用一个脚本文件中的另一段代码,将创建一个单独的
shell,因而去除所有原脚本中定义的存在变量。
函数可以放在同一个文件中作为一段代码,也可以放在只包含函数的单独文件中。函数
不必包含很多语句或命令,甚至可以只包含一个 echo语句,这取决于使用者。
203
第19章 shell 函 数
下载
19.1 在脚本中定义函数
以下是一个简单函数
所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell解释器
首次发现它时,才可以使用。调用函数仅使用其函数名即可。上面的例子中,函数名为 hello,
函数体包含一个echo语句,反馈当天日期。
19.2 在脚本中使用函数
现在创建函数,观察其在脚本中的用法。
运行脚本,结果为:
上面例子中,函数定义于脚本顶部。可以在脚本中使用函数名 hello调用它。函数执行后,
控制返回函数调用的下一条语句,即反馈语句 back from the function。
19.3 向函数传递参数
向函数传递参数就像在一般脚本中使用特殊变量 $1,$2...$9一样,函数取得所传参数后,
将原始参数传回 shell脚本,因此最好先在函数内重新设置变量保存所传的参数。这样如果函
数有一点错误,就可以
通过已经本地化的变量名迅速加以跟踪。函数里调用参数(变量)的
转换以下划线开始,后加变量名,如: _FILENAME或_filename。
19.4 从调用函数中返回
当函数完成处理或希望函数基于某一测试语句返回时,可做两种处理:
1) 让函数正常执行到函数末尾,然后返回脚本中调用函数的控制部分。
2) 使用return返回脚本中函数调用的下一条语句,可以带返回值。 0为无错误,1为有错误。
这是可选的,与最后状态命令报表例子极其类似。其格式为:
204
第四部分 基础shell编程
下载
return 从函数中返回, 用最后状态命令决定返回值。
Return 0 无错误返回。
Return 1 有错误返回
19.5 函数返回值测试
可以直接在脚本调用函数语句的后面使用最后状态命令来测试函数调用的返回值。例如:
更好的办法是使用if语句测试返回0或者返回1。最好在if语句里用括号将函数调用括起来
以增加可读性。例如:
如果函数将从测试结果中反馈输出,那么使用替换命令可保存结果。函数调用的替换格
式为:
variable_name=function_name
函数function_name输出被设置到变量variable_name中。
不久我们会接触到许多不同的函数及使用函数的返回值和输出的不同方法。
19.6 在shell中使用函数
当你收集一些经常使用的函数时,可以将之放入函数文件中并将文件载入 shell。
文件头应包含语句#!/bin/sh,文件名可任意选取,但最好与相关任务有某种实际联系。例
如,functions.main。
一旦文件载入shell,就可以在命令行或脚本中调用函数。可以使用 set命令查看所有定义
的函数。输出列表包括已经载入shell的所有函数。
如果要改动函数,首先用 unset命令从shell中删除函数,尽管unset删除了函数以便于此函
数对于shell或脚本不可利用,但并不是真正的删除。改动完毕后,再重新载入此文件。有些
shell会识别改动,不必使用unset命令,但为了安全起见,改动函数时最好使用 unset命令。
19.7 创建函数文件
下面创建包容函数的函数文件并将之载入 shell,进行测试,再做改动,之后再重新载入。
函数文件名为functions.main,内容如下:
205
第19章 shell 函 数
下载
上述脚本本书前面用过,现在将之转化为一个函数。这是一个基本 find命令的前端。如果
不加
参数,函数将返回 1,即发生错误。注意错误语句中用到了实际函数名,因为这里用 $0,
shell将只返回sh-信息,原因是文件并不是一个脚本文件。这类信息对用户帮助不大。
19.8 定位文件
定位文件格式为:
./pathname/filename
现在文件已经创建好了,要将之载入 shell,试键入:
$. functions.main
如果返回信息file not found,再试:
$. /functions.main
此即<点><空格><斜线><文件名>,现在文件应该已载入shell。如果仍有错误,则应该仔
细检查是否键入了完整路径名。
19.9 检查载入函数
使用set命令确保函数已载入。set命令将在shell中显示所有的载入函数。
19.10 执行shell函数
要执行函数,简单地键入函数名即可。这里是带有一个参数的 findit函数,参数是某个系
统文件。
206
第四部分 基础shell编程
下载
19.10.1 删除shell函数
现在对函数做一些改动。首先删除函数,使其对 shell不可利用。使用unset命令完成此功
能。删除函数时unset命令格式为:
unset function_name
$ unset findit
如果现在键入set命令,函数将不再显示。
19.10.2 编辑shell函数
编辑函数functions.main,加入for循环以便脚本可以从命令行中读取多个参数。改动后函
数脚本如下:
再次定位函数
$. /functions.main
使用set命令查看其是否被载入,可以发现 shell正确解释for循环以接受所有输入参数。
现在执行改动过的findit函数,输入两个参数:
207
第19章 shell 函 数
下载
19.10.3 函数举例
既然已经学习了函数的基本用法,现在就用它来做一些工作。函数可以节省大量的编程
时间,因为它是可重用的。
1. 变量输入
以下脚本询问名,然后是姓。
要求输入字符必须只包含字母。如果不用函数实现这一点,要写大量脚本。使用函数可
以将重复脚本删去。这里用awk语言测试字符。以下是取得只有小写或大写字符的测试函数。
首先设置变量$1为一有意义的名字,然后用 awk测试整个传送记录只包含字母,此命令输
出(1为非字母,空为成功)保存在变量 _LETTERS_ONLY中。
然后执行变量测试,如果为空,则为成功,如果有值,则为错误。基于此项测试,返回
码然后被执行。在对脚本的函数调用部分进行测试时,使用返回值会使脚本清晰易懂。
使用if语句格式测试函数功能:
如果有错误,可编写一个函数将错误反馈到屏幕上。
208
第四部分 基础shell编程
下载
函数name_error用于显示所有无效输入错误。使用特殊变量 $@显示所有参数,这里为变
量F_NAME和S_NAME值。完成脚本如下:
注意每个输入的while循环,这将确保不断提示输入直至为正确值,然后跳出循环。当然,
209
第19章 shell 函 数
下载
实际脚本拥有允许用户退出循环的选项,可使用适当的游标,正像控制 0长度域一样。
2. echo问题
echo语句的使用类型依赖于使用的系统是 LINUX、BSD还是系统V,本书对此进行了讲解。
下面创建一个函数决定使用哪种echo语句。
使用echo时,提示应放在语句末尾,以等待从 read命令中接受进一步输入。
LINUX和BSD为此使用echo命令-n选项。
以下是LINUX(BSD)echo语句实例,这里提示放于echo后面:
系统V使用\C保证在末尾提示:
在echo语句开头LINUX使用-e选项反馈控制字符。其他系统使用反斜线保证 shell获知控
制字符的存在。
有两种方法测试echo语句类型,下面讲述这两种方法,这样,就可以选择使用其中一个。
第一种方法是在echo语句里包含测试控制字符。如果键入\007和一个警铃,表明为系统V,
如果只键入\007,显示为LINUX。
以下为第一个控制字符测试函数。
注意这里又用到了特殊变量$@以反馈字符串,要在脚本中调用上述函数,可以使用:
uni_prompt "\007 there goes the bell ,What is your name:"
这将发出警报并反馈‘ What is your name:’,并在行尾显示字符串。如果在末尾出现字
符,则为系统V版本,否则为LINUX/BSD版本。
第二种方法使用系统V \c测试字母z是否悬于行尾。
210
第四部分 基础shell编程
下载
要在脚本中调用上述函数,可以使用:
uni_prompts "\007 there goes the ,bellWhat is your name:"
使用两个函数中任意一个,并加入一小段脚本:
将产生下列输出:
There goes the bellWhat is your name:
,
3. 读单个字符
在菜单中进行选择时,最麻烦的工作是必须在选择后键入回车键,或显示“ press any key
to continue”。可以使用dd命令解决不键入回车符以发送击键序列的问题。
dd命
令常用于对磁带或一般的磁带解压任务中出现的数据问题提出质疑或转换,但也可
用于创建定长文件。下面创建长度为 1兆的文件myfile。
dd if:/dev/zero of=myfile count=512 bs=2048
dd命令可以翻译键盘输入,可被用来接受多个字符。这里如果只要一个字符, dd命令需
要删除换行字符,这与用户点击回车键相对应。 dd只送回车前一个字符。在输入前必须使用
stty命令将终端设置成未加工模式,并在 dd执行前保存设置,在dd完成后恢复终端设置。
函数如下:
要调用函数,返回键入字符,可以使用命令替换操作,例子如下:
211
第19章 shell 函 数
下载
4. 测试目录存在
拷贝文件时,测试目录是否存在是常见的工作之一。以下函数测试传递给函数的文件名
是否是一个目录。因为此函数返回时带有成功或失败取值,可用 if语句测试结果。
函数如下:
要调用函数并测试结果,可以使用:
5. 提示Y或N
许多脚本在继续处理前会发出提示。大约可以提示以下动作:
创建一个目录。
是否删除文件。
是否后台运行。
确认保存记录。
等等
以下函数是一个真正的提示函数,提供了显示信息及缺省回答方式。缺省回答即用户按
下回车键时采取的动作。case语句用于捕获回答。
212
第四部分 基础shell编程
下载
要调用上述函数,须给出显示信息或参数 $1,或字符串变量。缺省回答 Y或N方式也必须
指定。
以下是几种函数continue_prompt的调用格式。
在脚本中加入上述语句,给出下列输入:
现在可以看出为什么函数要有指定的缺省回答。
以下是函数调用的另一种方式:
213
第19章 shell 函 数
下载
也可以使用字符串变量$1调用此函数:
6. 从登录ID号中抽取信息
当所在系统很庞大,要和一登录用户通信时,如果忘了用户的全名,这是很讨厌的事。
比如有时你看到用户锁住了一个进程,但是它们的用户 ID号对你来说没有意义,因此必须要
用grep passwd文件以取得用户全名,然后从中抽取可用信息,向其发信号,让其他用户开锁。
以下函数用于从grep /etc/passwd命令抽取用户全名。
本系统用户全名位于passwd文件域5中,用户的系统可
能不是这样,这时必须改变其域号
以匹配passwd文件。
这个函数需要一个或多个用户ID号作为参数。它对密码文件进行grep操作。
函数脚本如下:
以下为whois函数调用方式:
7. 列出文本文件行号
在vi编辑器中,可以列出行号来进行调试,但是如果打印几个带有行号的文件,必须使用
nl命令。以下函数用nl命令列出文件行号。原始文件中并不带有行号。
214
第四部分 基础shell编程
下载
要调用number_file函数,可用一个文件名做参数,或在 shell中提供一文件名,例如:
$ number_file myfile
也可以在脚本中这样写或用:
number_file $1
输出如下:
8. 字符串大写
有时需要在文件中将字符串转为大写,例如在文件系统中只用大写字符创建目录或在有
效的文本域中将输入转换为大写数据。
以下是相应功能函数,可以想像要用到 tr命令:
变量 upper保存返回的大写字符串,注意这里用到特定参数 $ @来传递所有参数。
str_to_upper可以以两种方式调用。在脚本中可以这样指定字符串。
或者以函数输入参数$1的形式调用它。
215
第19章 shell 函 数
下载
两种方法均可用替换操作以取得函数返回值。
9. is_upper
虽然函数str_to_upper做字符串转换,但有时在进一步处理前只需知道字符串是否为大写。
is_upper实现此功能。在脚本中使用 if语句决定传递的字符串是否为大写。
函数如下:
要调用is_upper,只需给出字符串参数。以下为其调用方式:
要测试字符串是否为小写,只需在函数 is_upper中替换相应的 awk语句即可。此为
is_lower。
10. 字符串小写
现在实现此功能,因为已经给出了 str_to_upper,最好相应给出str_to_lower。函数工作方
式与前面一样。
函数如下:
216
第四部分 基础shell编程
下载
变量 LOWER保存最近返回的小写字符串。注意用到特定参数 $@传递所有参数。
str_to_lower调用方式也分为两种。可以在脚本中给出字符串:
或在函数中用参数代替字符串:
11. 字符串长度
在脚本中确认域输入有效是常见的任务之一。确认有效包括许多方式,如输入是否为数
字或字符;
域的格式与长度是否为确定形式或值。
假定脚本要求用户交互输入数据到名称域,你会想控制此域包含字符数目,比如人名最
多为20个字符。有可能用户输入超过 50个字符。以下函数实施控制功能。需要向函数传递两
个参数,实际字符串和字符串最大长度。
函数如下:
调用函数check_length:
217
第19章 shell 函 数
下载
循环持续直到输入到变量 NAME的数据小于最大字符长度,这里指定为 10,break命令然
后跳出循环。
使用上述脚本段,输出结果如下:
可以使用wc命令取得字符串长度。但是要注意,使用 wc命令接受键盘输入时有一个误操
作。如果用户输入了一个名字后,点击了几次空格键, wc会将这些空格也作为字符串的一部
分,因而给出其错误长度。awk在读取键盘时缺省截去字符串末尾处空格。
以下是wc命令的缺点举例(也可以称为特征之一)
运行上述脚本(其中□为空格)
12. chop
chop函数删除字符串前面字符。可以指定从第一个字符起删去的字符数。假定有字符串
MYDOCUMENT.DOC,要删去MYDUCUMENT部分,以便函数只返回 .DOC,需要把下述命
令传给chop函数:
MYDOCUMENT.DOC 10
Chop 函数如下:
218
第四部分 基础shell编程
下载
删除后字符串保存于变量CHOPPED中,使用下面方法调用chop函数:
或者:
13. MONTHS
产生报表或创建屏幕显示时,为方便起见有时要快速显示完整月份。函数 months,接受
月份数字或月份缩写作为参数,返回完整月份。
例如,传递3或者03可返回March。函数如下:
219
第19章 shell 函 数
下载
用下面方法调用函数months
months 04
上面例子显示April,脚本中使用:
返回月份June。
19.10.4 将函数集中在一起
本章目前讲到的函数没有一定的顺序。这些例子只表明函数不一定很长或不一定为一些
复杂的脚本。
本书许多函数脚本简单实用,并不需要任何新的后备知识。这些函数只是防止重复输入
脚本,实际上这就是函数的基本功能。
本章开始部分,讲到怎样在 shell中使用函数。第一次使用函数时,也许要花一段时间才
能理解其返回值的用法。
本章讲到了几种不同的调用函
数及其返回值的方法。如果遇到问题,查看一下实例返回
值及其测试方法即可。
以下是一些小技巧。测试函数时,首先将其作为代码测试,当结果满意时,再将其转换
为函数,这样做可以节省大量的时间。
19.11 函数调用
本章最后讲述使用函数的两种不同方法:从原文件中调用函数和使用脚本中的函数。
19.11.1 在脚本中调用函数
要在脚本中调用函数,首先创建函数,并确保它位于调用之前。以下脚本使用了两个函
数。此脚本前面提到过,它用于测试目录是否存在。
220
第四部分 基础shell编程
下载
上述脚本中,两个函数定义于脚本开始部分,并在脚本主体中调用。所有函数都应该在
任何脚本主体前定义。注意错误信息语句,这里使用函数 error_msg显示错误,反馈所有传递
到该函数的参数,并加两声警报。
19.11.2 从函数文件中调用函数
前面讲述了怎样在命令行中调用函数,这类函数通常用于系统报表功能。
现在再次使用上面的函数,但是这次将之放入函数文件 functions.sh里。sh意即shell脚本。
221
第19章 shell 函 数
下载
现在编写脚本就可以调用 functions.sh中的函数了。注意函数文件在脚本中以下述命令格
式定位:
.\
使用这种方法不会创建另一个shell,所有函数均在当前shell下执行。
运行上述脚本,可得同样输出结果,好像函数在脚本中一样。
222
第四部分 基础shell编程
下载
19.12 定位文件不只用于函数
定位文件不只针对于函数,也包含组成配置文件的全局变量。
假定有两个备份文件备份同一系统的不同部分。最好让它们共享一个配置文件。为此需
要在一个文件里创建用户变量,然后将一个备份脚本删除后,可以载入这些变量以获知用户
在备份开始前是否要改变其缺省值。有时也许要备份到不同的媒体中。
当然这种方法可用于共享配置以执行某一过程的任何脚本。下面的例子中,配置文件
backfunc包含一些备份脚本所共享的缺省环境。文件如下:
缺省文件很清楚,第1域_CODE包含一个脚本关键字。要查看并且改变缺省值,用户必须
首先输入匹配_CODE取值的脚本,即“comet”。
以下脚本要求输入密码,成功后显示缺省配置。
脚
本运行时,首先要求输入脚本。脚本匹配后,可以查看缺省值。然后就可以编写脚本
让用户改变缺省值。
223
第19章 shell 函 数
下载
19.13 小结
使用函数可以节省大量的脚本编写时间。创建可用和可重用的脚本很有意义,可以使主
脚本变短,结构更加清晰。
当创建了许多函数后,将之放入函数文件里,然后其他脚本就可以使用这些函数了。