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

在上一章当中我们讲述了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,以此类推。同时还可以在函数中使用$*或[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

时间: 2024-10-19 07:34:15

2018-7-10bash编程之case及函数的相关文章

SHELL编程之case与函数

一.case语句概述 使用case语句改写if多分支可以使脚本结构更加清晰.层次分明 针对变量不同取值,执行不同的命令序列 case语句结构如下: case 变量值  in 模式1) 命令序列1 ;; 模式2) 命令序列2 ;; *) 默认命令序列 esac 1 #!/bin/bash 2 read -p "请输入一个字符: " key 3 case "$key" in 4 [a-z]|[A-Z]) #假如变量$key在26个大小写英文字母中 5 echo &quo

Linux/Unix C编程之的perror函数,strerror函数,errno

#include <stdio.h> // void perror(const char *msg); #include <string.h> // char *strerror(int errnum); #include <errno.h> //errno ? errno 是错误代码,在 errno.h头文件中: perror是错误输出函数,输出格式为:msg:errno对应的错误信息(加上一个换行符): strerror?是通过参数 errnum (就是errno)

我也来谈javascript高级编程之:javascript函数编译过程

前言 题目有点大,其实也就是手痒...跟大家来扯一下javascript编译过程. 那么到底什么是"编译"呢 这个...本人文笔太差,我还是直接举例子吧. 相信玩过js童鞋应该都看过下面这样一个面试题: var a=3; function fn(){ alert(a); function a(){ a=5; } a(); alert(a); } fn(); alert(a); 请问上面的题目执行结果如何呢? 各位童鞋答对了没.没答对?...没关系.别急.下面进一段js科普: 各位童鞋都

shell编程之case分支语句

shell编程之case分支语句 case分支语句和if的多分支语句很相似. if多分支语句一般用在有(区间范围)的地方 :例如:0-100之间. if需要判断多个不同的条件. case的分支语句用在 如果里面匹配到固定值:或某个变量存在多中取值,需要对其中的每一个取值分别执行不同的命令序列. case只是判断一个变量的不同取值. case的模式: 执行流程: 首先使用"变量值"与模式1进行比较,如果取值相同就执行模式1的命令序列1,直到遇见" ;; "后跳转到es

Shell编程之case多分支语句、循环语句(for、while等)、Shell函数、Shell数组

case多分支语句 case语句的结构: 针对变量的不同取值,分别执行不同的命令列 case 变量值 in 模式1) 命令序列1 ;; 模式2) 命令序列2 ;; ..... *) 默认命令序列 esac 示例: 字符类型识别: 提示用户输入一个字符: 判断出该字符是字母.数字或者其他字符. 脚本如下: [[email protected] opt]# vim test01.sh #!/bin/bash read -p "请输入一个字符:" key case $key in [a-z]

2016-10-3 linux基础学习——脚本编程之while循环/函数

脚本编程控制结构:    顺序    选择        if        case    循环        for        while        until    1.while Command; do    statmentdone    进入循环:条件满足    退出循环:条件不满足 2.until Command; do    statment    ..done 进入循环:条件不满足    退出循环:条件满足    3.for ((expr1; expr2; expr3

bash脚本编程之case语句及脚本选项进阶

case语句及脚本选项进阶详解     面向过程程序设计语言中的控制流(即程序当中的语句)默认是顺序执行的. 程序设计语言的控制结构一共有三类: 1,顺序结构 2,选择结构 (1)if语句 ->单分支的if语句 格式:if condition1;then expression ... fi ->双分支的if语句 格式:if condition1;then expression ... else expression ... fi ->多分支的if语句 格式:if condition1;t

8Linux服务器编程之:chdir()函数和cd命令,getcwd()函数和pwd

 1chdir依赖的头文件 #include<unistd.h> 2函数定义 int chdir(const char *path); int fchdir(int fd); 函数说明: chdir的作用是改变工作目录 4getcwd依赖的头文件 #include<unistd.h> 5函数定义: char *getcwd(char *buf, size_t size); 函数说明,通过这个函数获得路径 6.案例说明: 7.pathconf依赖的头文件 #include<

10Linux服务器编程之:opendir()函数,readdir()函数,rewinddir()函数,telldir()函数和seekdir()函数,closedir()函数

 1 opendir所需的头文件 #include<sys/types.h> #include<dirent.h> 2函数声明 DIR *opendir(const char *name); DIR *fdopendir(int fd); 通过opendir来打开一个文件夹 3readdir依赖的头文件 #include<dirent.h> 4函数声明 struct dirent *readdir(DIR *dirp); int readdir_r(DIR *dir