在上一章当中我们讲述了bash循环,其中我们讲述了for
循环的特殊用法,以及while
循环的特殊用法,而在此前我们讲述了循环的控制语句,一个是break
,另一个是continue
,对于continue来说,它是结束本轮循环而后进入下一轮循环,而break是提前结束其循环本身。但如果是循环嵌套的话,break只能退出当前那一层的循环,如果想退出所有的循环,就要使用break后面加上一个数字用来跳出循环的层数。而后while
循环的特殊用法可以遍历文件的每一行,for以及while循环可以写成:
for (()); do 循环体 done while read VARIABLE; do 循环体 done < /PATH/TO/SOMEFILE
一、case语句
我们此前讲到过,任何一个程序控制语言,在执行该程序时,都由顺序、选择及循环这三种组成,而对于判断条件的语句来说,主要有if
和case
,我们回顾一下多分支的if语句组成格式为:
多分支if语句: if CONDITION1; then 分支1 elif CONDINTION2; then 分支2 ... else 分支n fi
示例1:显示一个菜单给用户:
cpu) display cpu information mem) display mem information disk) display disks information quit) quit 要求:(1) 提示用户给出自己的选择; (2) 正确的选择则给出相应的信息;否则,重新提示让其用户选择正确的选项
那么以上我们使用的是if控制语句写出的bash程序,接下来我们了解一下case语句的使用,其语法格式为:
case语句的语法格式: case $VARIABLE in PAT1) 分支1 ;; PAT2) 分支2 ;; ... *) 分支n ;; esac
每一个分支都要使用双分号结尾,这也是固定的语法格式,而这个双分号可以单独成行,也可以加载分支语句的后面,但是不能省略,因为省略的话,即便模式匹配,也会判断下一个模式。不过虽然支持通配,但仅能支持glob
的通配符。
case支持glob风格的通配符: *:任意长度的任意字符; ?:任意单个字符; []:范围内任意单个字符; a|b:a或b;
那么现在我们更改一下上面的示例,更改为case语句使得来回作为比较。
#!/bin/bash # cat << EOF cpu) display cpu information. mem) display memory information. disk) display disk information. quit) quit ============================================== EOF read -p "Please input option: " option while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ]; do echo "Error! Please input information." read -p "Enter your option: " option done case $option in cpu) lscpu ;; mem) free -m ;; disk) fdisk -l /dev/[hs]d[a-z] ;; quit) echo "quit!" exit 0 ;; esac
与多分支的if语句相比简单很多,case语句在很多的场景当中,能够替换多分支的if,写为最简洁的格式,但只能用于一个变量对于多个值做模式匹配才可以使用。
示例:写出一个服务框架脚本;
$lockfile, 值/var/lock/subsys/SCRIPT_NAME (1) 此脚本可接受start, stop, restart, status四个参数之一; (2) 如果参数非此四者,则提示帮助后退出; (3) start, 则创建lockfile,并显示启动,stop, 则删除lockfile,并显示停止;restart, 则先删除文件在创建此文件,而后显示重启完成;status, 如果lockfile存在,则显示running,否则,则显示为stopped。
以上就是case语句的示例,看上去比多分支的语句要简便了许多,但是有些代码还是重复了使用,而重复使用的后果使得脚本会变得臃肿,所以我们必须使用代码某种手段能够将代码重用,而函数就是将代码能够实现重用及模块和结构化的编程。
二、函数
那么函数对于过程式编程来说,实现的是代码重用的一个功能组件,它能实现模块和结构化编程,那么对于函数来讲,我们可以理解为:
把一段独立功能的代码当作一个整体(用大括号圈起来),并为之起一个名字;而命名的代码片段,此即为函数;
我们需要注意的是:
注意:定义函数的代码段不会自动执行,在调用时才能执行;所谓调用函数,是指在执行的代码段中给定函数名即可; 函数名出现的任何位置,在代码执行时,都会被自动替换为函数代码;
由于函数是一个独立而又完整的功能,因此就会引进新的上下文,那么函数就是将一段拥有独立功能的代码或命令使用{}
进行封装,等待被执行调用,而函数名即为就是调用该函数代码时的名称,将函数名引入到执行代码时则会被调用,而在执行时则会自动替换为函数代码执行,所以它是能够完成模块化与结构化编程的一个重要组件,而主要的实现就是代码重用,将其一段代码可调用n多次。
那么如何定义其函数语法,其共有两种方式:
语法一: function f_name { ...函数体... } 语法二: f_name() { ...函数体... }
那么再次强调的是,函数只能被调用,才会执行,而调用的方式就是给定其函数名即可,函数名出现的地方则会自动替换其函数代码。
函数是有其生命周期的,就是每一次被调用时才会创建该函数,而执行完成返回给执行代码的时候则会终止;
其实在我们执行bash脚本时,都会有一个命令的状态值或者为返回值,而函数也有返回值,共有两个,一种是使用echo
或者是print
f的结果返回,还有一种就是状态返回值,其状态返回的就是函数体中的最后一条命令运行的状态结果。
当然,对我们来讲其实并不理想,所以我们可能需要自定义状态返回值。
函数的生命周期:每次被调用时创建,返回时终止; 其状态返回值为函数体中运行的最后一条命令的状态结果; 自定义状态返回值,需要使用:return 0: 成功; 1-255: 失败;
不过需要注意的是,在函数中的任何位置但凡遇见return,则函数生命周期就执行结束,即使该函数代码并不是最后一个语句,就像脚本语句,一旦遇见exit
,则后续的脚本程序也无法运行。
示例:给定一个用户名;取得用户的id号和默认的shell。
#!/bin/bash #
但是这段代码还是不够灵活,因为脚本的交互式限制了多个用户查询,即使可以允许多个用户查询,每次在read命令中还得需要添加变量,这是非常麻烦的,所以我们尽可能的避开,那么另一个示例代码为:
#!/bin/bash #
需要注意的是,在函数中$1变量有特殊的意义。
以上我们了解到函数的作用及用法之后,我们将之前的服务框架脚本使用函数的方式来进行重写。
示例:使用函数来重写服务框架脚本:
#!/bin/bash## chkconfig: - 44 55# prog=$(basename $0)lockfile=/var/lock/subsys/$prog start (){ if [ -f $lockfile ]; then echo "$prog started..." else touch $lockfile [ $? -eq 0 ] && echo "$prog start..." fi } stop(){ if [ -f $lockfile ]; then rm -rf $lockfile [ $? -eq 0 ] && echo "$prog stop..." else echo "$prog stop yet..." fi} status() { if [ -f $lockfile ]; then echo "$prog is running..." else echo "$prog is stopped..." fi} usage(){ echo "Usage: { start | stop | restart | status }" exit 3} case $1 in"start") start ;;"stop") stop ;;"restart") stop start ;;"status") status ;;*) usage ;;esac
接下来说一下函数返回值的问题,函数的返回值共有两种,一种是执行结果,另一种是状态结果,也就是结果状态码,那么如果想要使用函数返回值给予调用者,在调用者内部则可以通过某些功能用来取得,所谓函数出现并替换的地方,能够将其函数的代码执行结果放在主程序中,那么函数如何有执行结果共有以下几种方式:
函数返回值: 函数执行结果返回值: (1) 使用echo或printf命令进行输出; (2) 函数体中调用的命令的执行结果函数退出状态码;
还有就是函数的退出状态码,我们之前说过:
函数的退出状态码: (1) 默认取决于函数体中执行的最后一条命令的退出状态码; (2) 自定义:return
在以后写函数时,有必要显示给出return
用来自定义其退出状态码。
在函数中,$1
和$2
有特殊意义,因为函数也可以接受参数,而且传递参数给函数时,在函数内部中,$1
和$2
调用的是函数内部的参数,而不是脚本的参数。那么如何给函数传递参数是在函数名后面给定函数列表即可,例如:testfun arg1 arg2 arg3 ...
。
我们在函数体当中,如果使用的是$1
,就表示调用的是arg1
。同样,使用的是$2
,则调用的是arg2
,以此类推。同时还可以在函数中使用$*或[email protected]引用所有参数,而$#引用传递参数的个数。
函数可以接受参数: 传递参数给函数: 在函数体当中,可以使用$1, $2, ...引用传递给函数的参数;还可以在函数中使用$*或[email protected]引用所有参数,$#引用传递参数的个数; 在调用函数时,在函数名后面以空白给定参数列表即可,例如:testfun arg1 arg2 ...
所以说函数是可以很灵活的,在代码段当中能够实现某一完整的功能,但是到底执行什么样的操作,施加在那个对象身上,可取决于传递函数的参数来实现。对于函数能够施加参数而言,我们以下有个示例,以便于能够进行理解。
示例:添加10个用户,添加用户的功能使用函数实现,用户名做为参数传递给函数;
#!/bin/bash## 5 user exists#adduser() { if id $1 &> /dev/null; then return 5 else useradd $1 retval=$? return $retval fi} for i in {1..10}; do adduser ${1}${i} retval=$? if [ $retval -eq 0 ]; then echo "add user ${1}${i} finish." elif [ $retval -eq 5 ]; then echo "user ${1}${i} exists." else echo "Unknown Error." fidone
当$?
多次调用时,最好将其$?
的数值保存下来。不然的话,第一个条件测试完成之后,返回的是当时条件的测试值。
练习:
1、使用函数实现ping主机时测试主机的在线状态;主机地址通过参数传递给函数; 主程序:测试192.168.1.1-192.168.10.1范围内各个主机的在线状态; 2、打印NN乘法表;
三、变量作用域
我们此前说过变量共有三种类型,分别为环境变量、本地变量和局部变量,在这里我们说的是局部变量
,局部变量就是在函数内部所存在,其有效范围就是函数内部,函数创建并调用时则局部变量创建便引用,当函数生命周期结束时,则局部变量也会其自动销毁,需要注意的是,无需等待脚本运行结束,而是函数调用返回时,则变量就结束,而这种变量就叫做局部变量。
而定义局部变量的方法为:local VARIABLE=VALUE
。
变量作用域: 局部变量:作用域是函数的生命周期;在函数结束时被自动销毁; 定义局部变量的方法:local VARIABLE=VALUE
我们回顾一下本地变量的作用域:
本地变量:作用域是运行脚本的shell进程的生命周期;因此,其作用范围为当前shell脚本程序文件;
举个栗子:
#!/bin/bash# name=tom setname() { name=jerry echo "Function: $name"} setnameecho "Shell: $name"
运行之后会发现:
# bash local.sh Function: jerryShell: jerry
本来Shell应该是tom,结果为jerry,因为我们在函数中所调用的name,其实就是主程序的name,这表示当函数开始调用时,直接将主程序的变量的值改为了函数中变量的值,因此函数内部为jerry,在函数外部依然是jerry,第一行name变量为本地变量,而在函数中是可以调用本地变量的。
如果二者不互相受影响,在函数内部的name
变量之前加上local
。
#!/bin/bash# name=tom setname() { local name=jerry echo "Function: $name"} setnameecho "Shell: $name"
加上之后其运行结果为:
# bash local.sh Function: jerry Shell: tom
需要注意的是,如果主程序变量和函数变量相同且互不影响时,在函数里加local
对变量进行修饰。所以在写代码时,在函数中使用局部变量,不然会有一些故障需要手动排除,这是一个很麻烦的过程,有时候你也不知道那个bug
是出自在那里。除非和主程序交互,交换值,否则在函数中一定要用local
方式来使用局部变量。
四、函数递归
所谓递归就是自己不断的调用自己,而函数递归就是函数直接或间接调用自身,那么什么时候被用到,比如做阶乘的时候,以及做斐波那契数列的时候等等,可能会用到,比如说10的阶乘。
函数递归: 函数直接或间接调用自身; 10!=10*9!=10*9*8!=10*9*8*7!=...
那么如果用函数来进行表示的话,如何实现其递归的功能,假设这个数字为n,这个n能够返回n*(n-1)
,而后能够我们能够让其阶乘为n*(n-1)!=n*(n-1)*(n-2)!=
,而后再一次调用函数为n=1
终止,则意味这一层一层的做出计算,称之为递归返回,但是递归太多也是有很大的问题,数值太大的话,需要大量的内存空间来保留其中间值。
还有一种是斐波那契数列,其特性为第一个数为1,第二个数也为1,随后每一个值都是前两个值的和。
1, 1, 2, 3, 5, 8, 13, 21, ...
以下分别为两个示例,阶乘和斐波那契数列,在这里我们不多做阐述。
#!/bin/bash#fact() { if [ $1 -eq 0 -o $1 -eq 1 ]; then echo 1 else echo $[$1*$(fact $[$1-1])] fi} fact 9
#!/bin/bash# fab() { if [ $1 -eq 1 ]; then echo -n "1 " elif [ $1 -eq 2 ]; then echo -n "1 " else echo -n "$[$(fab $[$1-1])+$(fab $[$1-2])] " fi} for i in $(seq 1 $1); do fab "$i "doneecho
原文地址:http://blog.51cto.com/tianxie/2150198