经过一周脚本的折磨,觉得还是有一定的收获,所以就把一些不容易理解的并且容易忘记的难点做一个总结。shell脚本中主要有两大模块,第一就是流程控制的脚本,第二就是函数控制的脚本。

首先,流程控制包括顺序执行,选择执行,循环执行。主要的条件语句就是if。那就来先说说if语句吧!

1.if语句分为单分支,双分支和多分支,并且可以嵌套。

2.多分支分为如下几种情况

if 判断条件1; then

条件为真的分支代码

elif 判断条件2; then

条件为真的分支代码

elif 判断条件3; then

条件为真的分支代码

else

以上条件都为假的分支代码

fi 

逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句

------------下面来举一些多分支的示例吧

1.判断/var/目录下所有文件的类型

for i in /var/* ;do

        if [ -b $i ];then

        echo "$i块设备文件"

        elif [ -c $i ];then

        echo "$i字符文件"

        elif [ -d $i ];then

        echo "$i目录文件"

        elif [ -f $i ];then

        echo "$i普通文件"

    elif [ -h $i ];then

        echo "$i链接文件"

        elif [ -p $i ];then

        echo "$i管道文件"

        elif [ -s $i ];then

        echo "$i套接字文件"

        else

        echo "$i该文件不存在"

fi

done

其次,再来说说select选择语句,这个语句一般可以跟case语句合并使用,case和select的语法格式如下。

1.case的语法格式,支持glob风格的通配符:

*: 任意长度任意字符

?: 任意单个字符

[]:指定范围内的任意单个字符

a|b: a或b

case 变量引用 in

PAT1)

分支1

;;

PAT2)

分支2

;;

...

*)

默认分支

;;

esac 

2.select主要用于创建菜单,按数字顺序排列的菜单项将显示在标准输入上,并显示 PS3 提示符,等待用户输入用户输入菜单列表中的某个数字,执行相应的命令用户输入被保存在内置变量 REPLY 中

------------举例说明,打印菜单

PS3="please choose the menu: "

select menu in  mifan hulatang jiaozi lamian huimian quit

do

case $REPLY in

1|4)

    echo "the price is 20"

    ;;

2)

    echo "the price is 12"

    ;;

3|5)

    echo "the price is 30"

    ;;

6)

    break

    ;;

*)

    echo "no the option"

esac

done                

然后说一下用的比较多的循环语句。循环语句包括for循环和while循环。

  1. for循环语句

    °格式如下:for 变量名 in 列表;do
            循环体
            done
    °执行机制:依次将列表中的元素赋值给“变量名;每次赋值后即执行一次循环体;直到列表中的元素耗尽,循环结束。
    °列表生成方式:
            (1) 直接给出列表
            (2) 整数列表:
            (a) {start..end}
            (b) $(seq [start [step]] end)
            (3) 返回列表的命令
               $(COMMAND)
            (4) 使用glob, 如: *.sh
            (5) 变量引用;
               $@, $*

------------举例说明,添加10个用户user1-user10,密码为8位随机字符

for i in {1..10} ; do 

    useradd user$i

    echo "user$i is created..."

     password=`tr -dc 'A-Za-z1-9' < /dev/urandom |head -c8`

     echo $password | passwd --stdin $i &> /dev/null

done

------------举例说明,/etc/rc.d/rc3.d目录下分别有多个以K开头和以S开头的文件;分别读取每个文件,以K开头的输出为文件加stop,以S开头的输出为文件名加start,如K34filename stop S66filename start

for i in /etc/rc.d/rc3.d/[SK]* ;do

        if [ $(basename $i |cut -c1) == "k"  ];then

        echo `basename $i` stop

        else 

        echo `basename $i` start

        fi

done 

2.while循环语句

。格式如下:while CONDITION; do

循环体

done

。CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环,所以CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正。

-----------举例说明,编写脚本,利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值

  let i=0,min=max=$RANDOM                                     echo "$min"

while [ $i -lt 9 ];do

    ran=$RANDOM

    echo "$ran"

    if [ $ran -le $max ];then

    let max=ran

    fi

    if [ $ran -le $min ];then

    let min=ran

    fi

    let i+=1

done

echo "最大值是$max,最小值是$min"

-----------举例说明,后续六个字符串:efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、ad865d2f63是通过对随机数变量RANDOM随机执行命令:

echo $RANDOM|md5sum|cut –c1-10后的结果,请破解这些字符串对应的RANDOM值

 

num_array=(efbaf275cd 4be9c40b8b 44b2395c46 f8c8873ce0 b902c16c8

b ad865d2f63)

for raw_num in `seq 0 65535`;do

suijijiami=`echo $raw_num | md5sum |cut -c 1-10`

    for num in ${num_array[*]};do

        if [ "$suijijiami" == "$num" ];then                    

            echo "$raw_num*****>$num_array"

         fi

    done

done

解析:这一题其实不难,主要就是要会反向思维,因为这些字符串都是通过随机数加密后而成,我们都知道随机数的一共有65536个数字,也就是说这些加密后的字符串本身没有加密时肯定都是在0到65535之间,所以我们可以把所有的随机数按照题目要求的方式随机加密,然后用for语句匹配我们自己随机加密的字符串是否跟题目上的字符串相同,如果相同,就取出相同字符串所对应的随机数,然后就得出了结果。

函数的使用,函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分,函数和shell程序比较相似,区别在于:Shell程序在子Shell中运行而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改。

。函数声明最好定义在一个单独的文件中,不要加上shebang机制。然后调用直接调用函数source|. 函数名,因为函数本身不需要运行,只要在调用的的时候在shell脚本中运行就可以了。

。函数变量默认的是全局变量,所以要定义本地变量只影响函数内部,避免修改函数外的变量值。

。函数不能用exit退出,因为函数本身没有开启子进程会退出整个脚本

。函数的格式:func1 () { local name=mage;echo func1;echo $name; }

-----------举例说明

编写服务脚本/root/bin/testsrv.sh,完成如下要求

(1) 脚本可接受参数:start, stop, restart, status

(2) 如果参数非此四者之一,提示使用格式后报错退出

(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME,并显示“启动成功”

考虑:如果事先已经启动过一次,该如何处理?

(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME,并显示“停止完成”

考虑:如果事先已然停止过了,该如何处理?

(5) 如是restart,则先stop,再start

考虑:如果本来没有start,如何处理?

(6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAMEis running...”

如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is stopped...”

其中:SCRIPT_NAME为当前脚本名

. /etc/init.d/functions

scripts="/var/lock/subsys/`basename $0`" 

start(){

    if [ -e $scripts ];then

        echo "服务已经启动,不需要再次启动"

    else

        touch $scripts

        action  "启动成功" true

    fi

}

stop(){

    if [ -e $scripts ];then

     rm -f $scripts

    action  "停止完成" true

    else

    echo  "服务未启动,无需停止"

    fi

}

restart(){

    if [ -e $scripts ];then

        stop            

      start

    else

        start

    fi

}

status(){

    if [ -e $scripts ];then

    action "`basename $0` is running......" true

    else

    action "`basename $0` is stopped......" true

    fi

}

case $1 in

    "start")

    start

    ;;

    "stop")

    stop

    ;;

    "restart")

    restart

    ;;                                        

 "status")

    status

    ;;

    *)

    echo  "输入参数有误:`basename $0`  is stop|start|restart|status"

    ;;

esac               

解析:本题的要求就是模仿一个服务的各种状态,当启动的时候,存在这个服务脚本文件,则显示已经启动了不需要再次启动,如果不存在这个服务脚本就创建这个服务脚本文件,并且提示启动成功。在停止服务的时候有这个服务脚本就删除掉这个脚本,并显示停止成功,如果本身就没有这个文件,就显示服务本身就没有启动,无需停止。在重启服务的时候,如果有这个脚本就先停止服务然后重启,如果不存在这个脚本就直接重启。状态就是如果有这个文件就显示服务开启,如果没有就显示服务停止。

-----------举例说明

编写脚本/root/bin/copycmd.sh

(1) 提示用户输入一个可执行命令名称

(2) 获取此命令所依赖到的所有库文件列表

(3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下;如:/bin/bash ==> /mnt/sysroot/bin/bash

/usr/bin/passwd==> /mnt/sysroot/usr/bin/passwd

(4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下:如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2

(5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出

. /etc/init.d/functions

while true;

do

read -p "请输入一个可执行的命令(quit 退出):" command

if [ "$command" == "quit" ] ; then

    exit 1

else

    cmd_path=`which $command`

    mkdir -p /mnt/sysroot$cmd_path

    cp $cmd_path /mnt/sysroot$cmd_path && action " $cmd_path /mnt/sysroot$cmd_path " true

    list=`ldd /bin/ls |grep -o "/lib.* "|tr -d " "`

    [ -e /mnt/sysroot/lib64 -a -e /mnt/sysroot/lib ] || mkdir -p /mnt/sysroot/{lib64,lib}

    for i in $list;do

    cp $i /mnt/sysroot$i && action "$i  /mnt/sysroot$i " true

    done

fi

done              

解析:这题看起来麻烦,其实做的时候并不是很麻烦,就是执行一个脚本,复制命令的路径到指定的路径下,然后把库文件也同时复制过来。要求就是不改变原来的路径,并且提示用户退出的时候再退出,不提示就可以一直复制。有一个需要注意的地方就是如果多次复制一个命令的时候不要重复复制,避免出错。

最后一题还有些地方不是很完善,希望看到的大神帮忙指正。关于脚本的就先写到这里了,有时间再补充。。。。。。