码迷,mamicode.com
首页 > 其他好文 > 详细

2018-7-10bash编程之case及函数

时间:2018-07-25 22:45:05      阅读:257      评论:0      收藏:0      [点我收藏+]

标签:weight   unknown   语句   error   判断   语法   fdisk   文件的   表示   

  在上一章当中我们讲述了bash循环,其中我们讲述了for循环的特殊用法,以及while循环的特殊用法,而在此前我们讲述了循环的控制语句,一个是break,另一个是continue,对于continue来说,它是结束本轮循环而后进入下一轮循环,而break是提前结束其循环本身。但如果是循环嵌套的话,break只能退出当前那一层的循环,如果想退出所有的循环,就要使用break后面加上一个数字用来跳出循环的层数。而后while循环的特殊用法可以遍历文件的每一行,for以及while循环可以写成:

for (()); do
    循环体
done

while read VARIABLE; do
    循环体
done < /PATH/TO/SOMEFILE

一、case语句

  我们此前讲到过,任何一个程序控制语言,在执行该程序时,都由顺序、选择及循环这三种组成,而对于判断条件的语句来说,主要有ifcase,我们回顾一下多分支的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或者是printf的结果返回,还有一种就是状态返回值,其状态返回的就是函数体中的最后一条命令运行的状态结果。

  当然,对我们来讲其实并不理想,所以我们可能需要自定义状态返回值。

函数的生命周期:每次被调用时创建,返回时终止;
    其状态返回值为函数体中运行的最后一条命令的状态结果;
    自定义状态返回值,需要使用: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,以此类推。同时还可以在函数中使用$*或$@引用所有参数,而$#引用传递参数的个数。

函数可以接受参数:
    传递参数给函数:
        在函数体当中,可以使用$1, $2, ...引用传递给函数的参数;还可以在函数中使用$*或$@引用所有参数,$#引用传递参数的个数;
        在调用函数时,在函数名后面以空白给定参数列表即可,例如: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."
fi
done

  当$?多次调用时,最好将其$?的数值保存下来。不然的话,第一个条件测试完成之后,返回的是当时条件的测试值。

  练习:

    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"
}

setname
echo "Shell: $name"

  运行之后会发现:

# bash local.sh 
Function: jerry
Shell: jerry

  本来Shell应该是tom,结果为jerry,因为我们在函数中所调用的name,其实就是主程序的name,这表示当函数开始调用时,直接将主程序的变量的值改为了函数中变量的值,因此函数内部为jerry,在函数外部依然是jerry,第一行name变量为本地变量,而在函数中是可以调用本地变量的。

  如果二者不互相受影响,在函数内部的name变量之前加上local

#!/bin/bash
#

name=tom

setname() {
local name=jerry
echo "Function: $name"
}

setname
echo "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 "
done
echo


2018-7-10bash编程之case及函数

标签:weight   unknown   语句   error   判断   语法   fdisk   文件的   表示   

原文地址:http://blog.51cto.com/tianxie/2150198

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!