免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 52769 | 回复: 26

shell学习基础第十九篇-shell函数 [复制链接]

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 23:00 |显示全部楼层
本帖最后由 L_kernel 于 2010-11-20 23:02 编辑

本书目前为止所有脚本都是从头到尾执行。这样做很好,但你也许已经注意到有些脚本
段间互相重复。
s h e l l允许将一组命令集或语句形成一个可用块,这些块称为s h e l l函数。
本章内容有:
• 定义函数。
• 在脚本中使用函数。
• 在函数文件中使用函数。
• 函数举例。

函数由两部分组成:
函数标题。
函数体。
标题是函数名。函数体是函数内的命令集合。标题名应该唯一;如果不是,将会混淆结
果,因为脚本在查看调用脚本前将首先搜索函数调用相应的s h e l l。
定义函数的格式为:
函数名()
{
命令1
. . .
}
或者
函数名(){
命令1
. . .
}
两者方式都可行。如果愿意,可在函数名前加上关键字f u n c t i o n,这取决于使用者。
f u n c t i o n 函数名()
{ ...
}
可以将函数看作是脚本中的一段代码,但是有一个主要区别。执行函数时,它保留当前
s h e l l和内存信息。此外如果执行或调用一个脚本文件中的另一段代码,将创建一个单独的
s h e l l,因而去除所有原脚本中定义的存在变量。
函数可以放在同一个文件中作为一段代码,也可以放在只包含函数的单独文件中。函数
不必包含很多语句或命令,甚至可以只包含一个e c h o语句,这取决于使用者。

19.1 在脚本中定义函数

以下是一个简单函数
  1. hello()
  2. {
  3.         echo "Hello there today's date is `date`"
  4. }
复制代码
所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至s h e l l解释器
首次发现它时,才可以使用。调用函数仅使用其函数名即可。上面的例子中,函数名为h e l l o,
函数体包含一个e c h o语句,反馈当天日期。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 23:05 |显示全部楼层
19.2 在脚本中使用函数
现在创建函数,观察其在脚本中的用法。
  1. #!/bin/sh
  2. # func1.sh
  3. hello ()
  4. {
  5.         echo "Hello there today's date is `date`"
  6. }      
  7. echo "now going to the function hello"
  8. hello
  9. echo "back from the function"
复制代码
执行结果如下:
  1. now going to the function hello
  2. Hello there today's date is Sun Nov 21 23:04:18 HKT 2010
  3. back from the function
复制代码
上面例子中,函数定义于脚本顶部。可以在脚本中使用函数名h e l l o调用它。函数执行后,
控制返回函数调用的下一条语句,即反馈语句back from the function。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 23:05 |显示全部楼层
19.3 向函数传递参数
向函数传递参数就像在一般脚本中使用特殊变量$ 1 , $ 2 . . . $ 9一样,函数取得所传参数后,
将原始参数传回s h e l l脚本,因此最好先在函数内重新设置变量保存所传的参数。这样如果函
数有一点错误,就可以通过已经本地化的变量名迅速加以跟踪。函数里调用参数(变量)的
转换以下划线开始,后加变量名,如: _ F I L E N A M E或_ f i l e n a m e。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 23:06 |显示全部楼层
19.4 从调用函数中返回
当函数完成处理或希望函数基于某一测试语句返回时,可做两种处理:
1) 让函数正常执行到函数末尾,然后返回脚本中调用函数的控制部分。
2) 使用r e t u r n返回脚本中函数调用的下一条语句,可以带返回值。0为无错误,1为有错误。
这是可选的,与最后状态命令报表例子极其类似。其格式为:
r e t u r n 从函数中返回, 用最后状态命令决定返回值。
Return 0 无错误返回。
Return 1 有错误返回

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 23:10 |显示全部楼层
19.5 函数返回值测试
可以直接在脚本调用函数语句的后面使用最后状态命令来测试函数调用的返回值。例如:
  1. check_it_is_a_directory $FILENAME # this is the function call and check
  2. if [ $? == 0 ] # use the last status command now to test
  3. then   
  4.         echo "All is OK"
  5. else   
  6.         echo "Someting went wrong!"
  7. fi
复制代码
更好的办法是使用i f语句测试返回0或者返回1。最好在i f语句里用括号将函数调用括起来
以增加可读性。例如:

  1. if check_it_is_a_directory $FILENAME; then
  2.         echo "All is OK"
  3.         # do something ??
  4. else   
  5.         echo "Something went wrong !"
  6.         # do something ??
  7. fi
复制代码
如果函数将从测试结果中反馈输出,那么使用替换命令可保存结果。函数调用的替换格
式为:
v a r i a b l e n a m e = f u n c t i o n n a m e
函数f u n c t i o n n a m e输出被设置到变量v a r i a b l e n a m e中。
不久我们会接触到许多不同的函数及使用函数的返回值和输出的不同方法。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 23:11 |显示全部楼层
19.6 在shell中使用函数
当你收集一些经常使用的函数时,可以将之放入函数文件中并将文件载入s h e l l。
文件头应包含语句# ! / b i n / s h,文件名可任意选取,但最好与相关任务有某种实际联系。例
如,f u n c t i o n s . m a i n。
一旦文件载入s h e l l,就可以在命令行或脚本中调用函数。可以使用s e t命令查看所有定义
的函数。输出列表包括已经载入s h e l l的所有函数。
如果要改动函数,首先用u n s e t命令从s h e l l中删除函数,尽管u n s e t删除了函数以便于此函
数对于s h e l l或脚本不可利用,但并不是真正的删除。改动完毕后,再重新载入此文件。有些
s h e l l会识别改动,不必使用u n s e t命令,但为了安全起见,改动函数时最好使用u n s e t命令。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 23:14 |显示全部楼层
19.7 创建函数文件
下面创建包容函数的函数文件并将之载入s h e l l,进行测试,再做改动,之后再重新载入。
函数文件名为f u n c t i o n s . m a i n,内容如下:
  1. #!/bin/sh
  2. # functions.main.sh
  3. #
  4. # findit: this is front end for the basic find command
  5. findit() {
  6.         # findit
  7.         if [ $# -lt 1 ]; then
  8.                 echo "usage :findit file"
  9.                 return 1
  10.         fi      
  11.         find / -name $1 -print
  12. }  
复制代码
上述脚本本书前面用过,现在将之转化为一个函数。这是一个基本f i n d命令的前端。如果
不加参数,函数将返回1,即发生错误。注意错误语句中用到了实际函数名,因为这里用$ 0,
s h e l l将只返回s h -信息,原因是文件并不是一个脚本文件。这类信息对用户帮助不大。

论坛徽章:
0
发表于 2010-11-20 23:16 |显示全部楼层
感谢楼主!!!

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 23:17 |显示全部楼层
本帖最后由 L_kernel 于 2010-11-20 23:22 编辑

19.8 定位文件
定位文件格式为:
. / p a t h n a m e / f i l e n a m e
现在文件已经创建好了,要将之载入s h e l l,试键入:
$. functions.main
如果返回信息file not found,再试:
$. /functions.main
此即<点> <空格> <斜线> <文件名>,现在文件应该已载入s h e l l。如果仍有错误,则应该仔
细检查是否键入了完整路径名。

19.9 检查载入函数
使用s e t命令确保函数已载入。s e t命令将在s h e l l中显示所有的载入函数。
# set
Linux中似乎没有这个命令。也就是使用set无法看到载入的函数。不知道什么原因?

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-21 09:33 |显示全部楼层
19.10 执行shell函数
要执行函数,简单地键入函数名即可。这里是带有一个参数的f i n d i t函数,参数是某个系
统文件。
$ findit groups

19.10.1 删除shell函数
现在对函数做一些改动。首先删除函数,使其对s h e l l不可利用。使用u n s e t命令完成此功
能。删除函数时u n s e t命令格式为:
unset function_name
$ unset findit
如果现在键入s e t命令,函数将不再显示。

19.10.2 编辑shell函数
编辑函数f u n c t i o n s . m a i n,加入f o r循环以便脚本可以从命令行中读取多个参数。改动后函
数脚本如下:
  1. #!/bin/sh
  2. # functions.main.sh
  3. #
  4. # findit: this is front end for the basic find command
  5. findit() {
  6.         # findit
  7.         if [ $# -lt 1 ]; then
  8.                 echo "usage :findit file"
  9.                 return 1
  10.         fi
  11.         for loop
  12.         do      
  13.                 find / -name $loop -print
  14.         done   
  15. }
复制代码
再次定位函数
$. /functions.main.sh
使用s e t命令查看其是否被载入,可以发现s h e l l正确解释f o r循环以接受所有输入参数。
  1. #!/bin/sh
  2. # functions.main.sh
  3. #
  4. # findit: this is front end for the basic find command
  5. findit() {
  6.         # findit
  7.         if [ $# -lt 1 ]; then
  8.                 echo "usage :findit file"
  9.                 return 1
  10.         fi
  11.         for loop "$@"
  12.         do      
  13.                 find / -name $loop -print
  14.         done   
  15. }
复制代码
现在执行改动过的f i n d i t函数,输入两个参数:
$ findit LPSO.doc passwd
对应在Linux的默认bash中,我们应该在脚本的某处使用函数名字调用它,而不是是否看它载入。



19.10.3 函数举例
既然已经学习了函数的基本用法,现在就用它来做一些工作。函数可以节省大量的编程
时间,因为它是可重用的。
1. 变量输入
以下脚本询问名,然后是姓。
echo -n "What is your first name :"
read F_NAME
echo -n "What is your surname :"
read $S_NAME

要求输入字符必须只包含字母。如果不用函数实现这一点,要写大量脚本。使用函数可
以将重复脚本删去。这里用a w k语言测试字符。以下是取得只有小写或大写字符的测试函数。
  1. char_name()
  2. # char_name
  3. # to call: char_name string
  4. # check if $1 does indeed contain only characters a-z,A-Z
  5. {
  6.         # assign the argument across to new variable
  7.         _LETTERS_ONLY=$1
  8.         _LETTERS_ONLY=`echo $1 | awk '{if($0~/[^a-zA-Z]/) print "1"}'`
  9.         if [ "$_LETTERS_ONLY" != "" ]
  10.         then   
  11.                 # oops errors
  12.                 return 1
  13.         else   
  14.                 # contains only chars
  15.                 return 0
  16.         fi      
  17. }      
复制代码
首先设置变量$ 1为一有意义的名字,然后用a w k测试整个传送记录只包含字母,此命令输
出(1为非字母,空为成功)保存在变量_ L E T T E R S O N LY中。
然后执行变量测试,如果为空,则为成功,如果有值,则为错误。基于此项测试,返回
码然后被执行。在对脚本的函数调用部分进行测试时,使用返回值会使脚本清晰易懂。
使用i f语句格式测试函数功能:
  1.        if char_name $F_NAME
  2.         then   
  3.                 # all ok breakout
  4.                 break
  5.         else   
  6.                 name_error $FNAME
  7.         fi      
复制代码
如果有错误,可编写一个函数将错误反馈到屏幕上。
  1. name_error()
  2. # display an error message
  3. {
  4.         echo " $@ contains errors, it must contain only letters"
  5. }      
复制代码
函数n a m e e r r o r用于显示所有无效输入错误。使用特殊变量$ @显示所有参数,这里为变
量F N A M E和S N A M E值。完成脚本如下:
  1. #!/bin/sh
  2. # func2.sh
  3. char_name()
  4. # char_name
  5. # to call: char_name string
  6. # check if $1 does indeed contain only characters a-z,A-Z
  7. {
  8.         # assign the argument across to new variable
  9.         _LETTERS_ONLY=$1
  10.         _LETTERS_ONLY=`echo $1 | awk '{if($0~/[^a-zA-Z]/) print "1"}'`
  11.         if [ "$_LETTERS_ONLY" != "" ]
  12.         then   
  13.                 # oops errors
  14.                 return 1
  15.         else   
  16.                 # contains only chars
  17.                 return 0
  18.         fi      
  19. }      
  20. name_error()
  21. # display an error message
  22. {
  23.         echo " $@ contains errors, it must contain only letters"
  24. }      
  25. while :
  26. do
  27.         echo -n "What is your first name :"
  28.         read F_NAME
  29.         if char_name $F_NAME
  30.         then   
  31.                 # all ok breakout
  32.                 break
  33.         else   
  34.                 name_error $FNAME
  35.         fi      
  36. done   
  37. while :
  38. do
  39.         echo -n "What is your surname :"
  40.         read S_NAME
  41.         if char_name $S_NAME
  42.         then   
  43.                 # all ok breakout
  44.                 break
  45.         else   
  46.                 name_error $S_NAME
  47.         fi      
  48. done   
复制代码
注意每个输入的w h i l e循环,这将确保不断提示输入直至为正确值,然后跳出循环。当然,
实际脚本拥有允许用户退出循环的选项,可使用适当的游标,正像控制0长度域一样。
运行上述脚本的情况如下:
  1. What is your first name :Davi3d
  2.   contains errors, it must contain only letters
  3. What is your first name :David
  4. What is your surname :Tansley1
  5. Tansley1 contains errors, it must contain only letters
  6. What is your surname :Tansley
复制代码
2. echo问题
e c h o语句的使用类型依赖于使用的系统是L I N U X、B S D还是系统V,本书对此进行了讲解。
下面创建一个函数决定使用哪种e c h o语句。
使用e c h o时,提示应放在语句末尾,以等待从r e a d命令中接受进一步输入。
L I N U X和B S D为此使用e c h o命令- n选项。
以下是L I N U X(B S D)e c h o语句实例,这里提示放于e c h o后面:
$ echo -n "Your name :"
Your name :{{?}}

系统V使用\ C保证在末尾提示:
echo "Your name :\C"
Your name :♢

在e c h o语句开头L I N U X使用- e选项反馈控制字符。其他系统使用反斜线保证s h e l l获知控
制字符的存在。
有两种方法测试e c h o语句类型,下面讲述这两种方法,这样,就可以选择使用其中一个。
第一种方法是在e c h o语句里包含测试控制字符。如果键入\ 0 0 7和一个警铃,表明为系统V,
如果只键入\ 0 0 7,显示为L I N U X。
以下为第一个控制字符测试函数。
  1. uni_prompt()
  2. {
  3.         if [ `echo "\007"` == "\007" ] >/dev/null 2>&1
  4.                 # does a bell sound or are the characters just echoed??
  5.         then   
  6.                 # characters echoed, it's LINUX/BSD
  7.                 echo -e -n "$@"
  8.         else   
  9.                 # it's System V
  10.                 echo "$@\c"
  11.         fi      
  12. }      
复制代码
注意这里又用到了特殊变量$ @以反馈字符串,要在脚本中调用上述函数,可以使用:
uni_prompt "\007 there goes the bell ,What is your name:"
这将发出警报并反馈‘ What is your name:’,并在行尾显示字符串。如果在末尾出现字
符,则为系统V版本,否则为L I N U X / B S D版本。
第二种方法使用系统V \c测试字母z是否悬于行尾。
  1. uni_prompt()
  2. # uni_prompt
  3. # univeral prompt
  4. {
  5.         if [ `echo "Z\c"` == "Z" ] >/dev/null 2>&1
  6.                 # echo any character out, does it hang on to the end of line ??
  7.         then   
  8.                 # yes, it's System V
  9.                 echo "$@\c"
  10.         else   
  11.                 # No, it's LINUX, BSD
  12.                 echo -e -n "$@"
  13.         fi      
  14. }      
复制代码
要在脚本中调用上述函数,可以使用:
uni_prompts "\007 there goes the ,What is your name:"
使用两个函数中任意一个,并加入一小段脚本:
uni_prompt "\007 There goes the bell, What is your name :"
read NAME
将产生下列输出:
There goes the be,What is your name:

3. 读单个字符

在菜单中进行选择时,最麻烦的工作是必须在选择后键入回车键,或显示“ press any key
to continue”。可以使用d d命令解决不键入回车符以发送击键序列的问题。
d d命令常用于对磁带或一般的磁带解压任务中出现的数据问题提出质疑或转换,但也可
用于创建定长文件。下面创建长度为1兆的文件m y f i l e。
dd if:/dev/zero of=myfile count=512 bs=2048
d d命令可以翻译键盘输入,可被用来接受多个字符。这里如果只要一个字符, d d命令需
要删除换行字符,这与用户点击回车键相对应。d d只送回车前一个字符。在输入前必须使用
s t t y命令将终端设置成未加工模式,并在d d执行前保存设置,在d d完成后恢复终端设置。
函数如下:
  1. read_a_char()
  2. # read_a_char
  3. {
  4.         # save the settings
  5.         SAVEDSTTY=`stty -g`
  6.         # set terminal raw please
  7.         stty cbreak
  8.         # read and output only one character
  9.         dd if=/dev/tty bs=1 count=1 2> /dev/null
  10.         # retore terminal and restore stty
  11.         stty -cbreak
  12.         stty $SAVEDSTTY
  13. }
复制代码
要调用函数,返回键入字符,可以使用命令替换操作,例子如下:
  1. echo -n "Hit Any Key To Continue"
  2. character=`read_a_char`
  3. echo " In case you are wondering you pressed $character"
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP