shell 流程控制

来源:互联网 发布:雪梨淘宝店质量 编辑:程序博客网 时间:2024/05/22 10:49

【前言】

  之前写过一篇文章叫做 shell脚本的基础入门,既然已经入门了,那今天就来说说shell编程的进阶。

  我们知道,shell脚本可以用来帮助我们更快的提高工作效率,而在工作中,很多时候的工作的简单而复杂的。什么叫做简单而复杂?简单是说他的操作性很简单,只是一行命令或者两行命令搞定,而复杂是说可能由于工作的需要性,有时候这一条或者两行命令需要我们重复执行十遍百遍,如果说人工去一遍一遍的执行,那你也就基本可以放弃这个岗位了,所以我们需要借助脚本来帮助我们实现。

举个简单的经典面试题吧:计算1+2+…+100的值。

想想我们之前是怎么做的?在之前的学习中,我是这么做的

[root@localhost ~]# echo {1..100}|tr ' ' '+ '|bc5050

  虽然这也是一种方法,显然这并不是面试官出题的意图,体现不了你的真实水平,那么我们就引入今天的正题:shell编程高级进阶。

【进入正题】

过程性的编程语言分为三类:

顺序执行

选择执行

循环执行

所谓顺序执行,即在一个shell脚本中,按照你所写的命令一条一条 的按照顺序执行:

[root@localhost app]# vim test.sh#!/bin/bash                   echo i am $USER      useradd Tom                           #创建用户id Tom                                #显示用户信息[root@localhost app]# ./test.sh       #执行脚本i am rootuid=1021(Tom) gid=1021(Tom) groups=1021(Tom)

  以上这种普遍的脚本就是顺序执行,但是在一些情况下,我们需要让脚本进行自行判断,符合某种条件则执行某种操作,这种就是选择执行。首先来看if …..else…..语句。

if..else语句

单分支
   if 条件;then
    条件为真的分支代码
  fi
 双分支
   if 条件;then
    条件为真的分支代码
  else
    条件为假的分支代码
   fi
 多分支
   if 条件1;then
    条件为真的分支代码
   elif 条件2;then
    条件为真的分支代码
   else
    条件为假的分支代码
   fi
判断条件:每一次遇到为“真”的条件,执行其分支语句,然后结束整个进程。当然if语句是可以嵌套的。

练习:实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;并给用户设置随机生成的8位密码,初始化登陆时提示用户修改密码,显示添加的用户的id号等信息。

[root@localhost bin]# vim  createuser.sh#/bin/bashpsd=`cat /dev/random |tr -dc '[0-9A-Za-z]'|head -c 8`  ------>将随机生成的8位密码存储到变量中read -p "please input a username: " usrid $usr &> /dev/null                                   ------>判断用户是否存在if [ $? -eq 0 ];then                                   ------>如存在则显示用户已存在,不存在则执行else的分支代码   echo $usr has existelse   useradd $usr    echo $psd | passwd --stdin $usr &>/dev/null   echo $usr:$psd >> /app/passwdrandom                 ------>将用户名及密码输出到一个文件中   chage -d 0 $usr                                     ------>强制用户下次登陆时修改密码     echo "user $usr is created!"   id $usrfiunset psd usr                                          ------>删除变量[root@localhost bin]# ./createuser.shplease input a username: xiaowanguser xiaowang is created!uid=1022(xiaowang) gid=1022(xiaowang) groups=1022(xiaowang)

case语句

case 变量引用 in
 条件1)
  分支1
  ;;
 条件2)
  分支2
  ;;
 esac

练习:编写脚本/root/bin/yesorno.sh,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息。

[root@localhost bin]# vim yesorno.sh read -p "please input yes or no: " ansvar1=`  echo $ans |tr -t '[a-z]' '[A-Z]'`    ------>将用户输入转换为大写字母,以防用户输入含有大写字母case $var1 inY|YES)        echo you input yes        ;;N|NO)        echo you input no        ;;*)        echo please input a right answer        ;;esac[root@localhost bin]# ./yesorno.sh   please input yes or no: noyou input no[root@localhost bin]# ./yesorno.sh please input yes or no: yesyou input yes[root@localhost bin]# ./yesorno.sh please input yes or no: hahaplease input a right answer

  选择执行常用的就是以上两种了,都是比较简单易理解的,接下来看循环执行,自身觉得比较烧脑的来了~~~

循环执行是为了节省人力,将执行的命令进行循环操作以达到目的,首先介绍for循环。

for 循环

先来看for循环的语法:

for: for NAME [in WORDS … ] ; do COMMANDS; done
for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done    两种格式写法

NAME:指变量

[in WORDS … ]:执行列表

do COMMANDS:执行操作

done:结束符

执行条件:依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束。

还记得上面我们说的计算1+2+…+100的值吗?我们这次使用循环来计算~~~

[root@localhost bin]# sum=0;for i in {1..100};do let sum=sum+i;let i++;done;echo sum is $sumsum is 5050                     #写入脚本也同样适用[root@localhost bin]# sum=0;for ((i=1;i<=100;i++));do let sum+=i;done;echo sum is $sumsum is 5050

for循环有多种写法,以上这种是数字循环,还有以下几种:

字符循环#!/bin/bashfor i in `ls /app/`do    echo $i is file namedone#!/bin/bashlist="aa bb cc dd"for i in $listdo    echo $idone路径循环for file in /app/*do    echo "$file is file name" done

练习:打印99乘法表

思路:使用两层循环,内层的循环变量引用外层的循环变量。

[root@localhost bin]# vim for7_9x9.sh for i in {1..9}do        for j in `seq 1 $i`;do                echo -e "$i*$j=$[i*j]   \c\t"        done        echodoneunset i j[root@localhost bin]# ./for7_9x9.sh     1*1=12*1=2   2*2=43*1=3   3*2=6   3*3=94*1=4   4*2=8   4*3=12  4*4=165*1=5   5*2=10  5*3=15  5*4=20  5*5=256*1=6   6*2=12  6*3=18  6*4=24  6*5=30  6*6=367*1=7   7*2=14  7*3=21  7*4=28  7*5=35  7*6=42  7*7=498*1=8   8*2=16  8*3=24  8*4=32  8*5=40  8*6=48  8*7=56  8*8=649*1=9   9*2=18  9*3=27  9*4=36  9*5=45  9*6=54  9*7=63  9*8=72  9*9=81

while 循环

语法格式:

while: while COMMANDS; do COMMANDS; done
while ((: while(( exp1; exp2; exp3 )); do COMMANDS; done    两种格式写法

判断条件:当判断条件为真时,则会一直执行;判断条件为假,则会终止循环。

那么有一种语法就可以实现死循环:

while true ;do echo true;done
由于true 的返回值永远为真,那么使用while就会一直循环。

练习:编写脚本,求100以内所有正奇数之和。

[root@localhost bin]# vim while1_jishusum.sh sum=0i=1while [ $i -le 100 ];do        if [ $[$i%2] -eq 1 ];then                let sum=sum+i        fi                let i++doneecho 100以内所有正奇数之和是 $sumunset i sum[root@localhost bin]# ./while1_jishusum.sh 100以内所有正奇数之和是 2500

练习:打印国际棋盘

思路:国际棋盘为八行八列,以两个空格为一个盘格,打印空格底色实现棋盘效果。

[root@localhost bin]# vim whileqipan.sh#!/bin/bashi=1while ((i<=8));do        j=1        while ((j<=8));do                varnum=$[$[i+j]%2]            ------>计算行数和列数之和取余的值                if [ $varnum -eq 0 ];then                        echo -n -e "\033[41m  \033[0m\033"    ------>打印两个红色                elif [ $varnum -eq 1 ];then                        echo -n -e "\033[47m  \033[0m\033"    ------>打印两个白色                fi                let j++         done        let i++        echodoneunset i j             

*until循环

until和while的格式上是相同的,但是用法上正好相反。它的判断条件是当条件判断为真时退出循环。

*continue

continue为循环控制语句,用于循环体中,表示结束某一个符合条件的循环,结束的只是当前轮的循环。

*break

break用法同continue相同,不同的是它结束的是整个循环。

*select 用法

select 常用于菜单,常与case组合达到菜单的效果。

使用PS3,显示用户界面提示符,相等于read -p 的效果

$REPLY变量的select的内置变量,用来引用用户输入。

[root@localhost bin]# vim menu47.sh PS3="请选择菜单编号:"select menu in 米饭 炒菜 面条 馒头 退出docase $REPLY in1|4)        echo this price is 20        ;;2|3)        echo this price is 12        ;;5)        echo byebye        exit 2        ;;*)        echo no this number !        echo $menuesacdone[root@localhost bin]# menu47.sh     1) 米饭2) 炒菜3) 面条4) 馒头5) 退出请选择菜单编号:1    this price is 20请选择菜单编号:4this price is 20请选择菜单编号:2this price is 12请选择菜单编号:5byebye[root@localhost bin]# 

【函数】

  函数类似于shell脚本,里面存放了一些指令,与脚本不同的是,这些函数可以统一放在一个文件里,在脚本中可以调用这些函数,重复使用,效率较高。

函数的格式:

function function_name () {        一些指令,即函数体}关键字function表示定义一个函数,可以省略,后面是函数的名字,()是格式要求,函数体中具体写一些需要完成的指令。

调用:调用一个函数直接调用定义的函数名即可,与shell命令的用法相同。

返回值:

  函数的运行进程与当前shell是用一个进程,因此在函数体重不能使用exit退出函数体,这个关键字hi导致系统退出当前shell,因此函数有一个专用的返回命令return。在函数体中使用return,返回值在0~255之间,可以使用$?查看返回值。

局部变量local:

  既然我们上面说函数的运行进程与当前shell是用一个进程,那么我们思考一个问题:如果我们在函数体中定义一个变量,而这个变量恰好与函数体外的变量同名,如果我们在函数体中对于该变量重新定义,那是不是会修改函数体外的变量?例如:

[root@localhost app]# name=xiaoli[root@localhost app]# echo $namexiaoli[root@localhost app]# fun1 () { name=haha;echo my name is $name; }  [root@localhost app]# fun1           ------>调用func1函数my name is haha                      [root@localhost app]# echo $namehaha                                 ------>变量已经被重新赋值

  那么就提到了局部变量local的定义。使用local关键字可以定义函数体内的局部变量而不影响函数体外的全局变量:

[root@localhost app]# name=xiaoli[root@localhost app]# echo $namexiaoli[root@localhost app]# fun1 () { local name=haha;echo my name is $name; }                  ------>定义局部变量[root@localhost app]# fun1my name is haha[root@localhost app]# echo $namexiaoli                         ------>全局变量未被影响

查看函数:

declare -f 查看所有定义的函数

删除函数:

unset -f

练习:函数调用

[root@localhost app]# vim test.sh#!/bin/bashfunc_eg () {        echo This is first arg}func_eg                   ------>调用func_eg 函数echo This is second arg[root@localhost app]# ./test.shThis is first arg        ------>函数执行结果This is second arg       ------>脚本执行结果

函数同样也接受位置参数:

[root@localhost app]# vim test.sh    #!/bin/bashfunc_eg2 () {        echo first arg is  $1        echo second arg is $2        echo third arg is  $3        echo All args are  $*}func_eg2 35 87 18[root@localhost app]# ./test.shfirst arg is 35second arg is 87third arg is 18All args are 35 87 18

函数递归:函数可以实现直接或者间接的调用自身

最经典的例子就是汉诺塔的例子了,这里就不多实验了,有兴趣的同学欢迎百度~~

【数组】

变量是指存储单个元素的内存空间

数组是指存储多个元素的连续内存空间,相当于多个变量的集合。

在数组里的每个元素都有一个下标,即索引。数组中的索引分为普通索引和关联索引。

普通索引也称数组索引,即从编号0开始,递归增加。

关联索引是指不使用数组0开始的索引号,对索引采用自定义的格式,可以是abc单个字母,也可以是一个单词或者字符。

对于普通数组来说,如果索引号不连续,则称为数组的稀疏格式。

声明普通数组:declare -a (也可以不声明)

声明关联数组:declare -A (必须声明)

数组赋值:

一次赋值一个变量

array_name[1]=sunday

一次赋值全部变量

array_name=(1 2 3 4)

只赋值给特定变量

array_name=([0]="a" [3]="b")

调用数组:

${ARRAY_NAME[INDEX]}

注意:省略[INDEX]表示引用下标为0的元素

[root@centos6 ~]# test=(a b c d) [root@centos6 ~]# echo ${test}        ------> 省略下标号则默认引用下标为0的元素a[root@centos6 ~]# echo ${test[2]}     ------>注意这里,因为我们的下标号是从0开始,所以下标为2的元素实际是第三个元素哦c[root@centos6 ~]# echo ${test[0]}a

引用数组所有元素:两种方法

${array_name[*]}

${array_name[@]}

显示数组中元素的个数:

${#array_name[*]}

${#array_name[@]}

[root@centos6 ~]# echo ${test[*]} a b c d[root@centos6 ~]# echo ${test[@]}a b c d[root@centos6 ~]# echo ${#test[*]}4[root@centos6 ~]# echo ${#test[@]}4

利用上面所说,我们可以对数组追加元素:

[root@centos6 ~]# test[${#test[*]}]=e    ------>通过引用数组元素的个数实现向数组中追加元素[root@centos6 ~]# declare -a             ------>查看定义的所有数组declare -a BASH_ARGC='()'declare -a BASH_ARGV='()'declare -a BASH_LINENO='()'declare -a BASH_SOURCE='()'declare -ar BASH_VERSINFO='([0]="4" [1]="1" [2]="2" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'declare -a DIRSTACK='()'declare -a FUNCNAME='()'declare -a GROUPS='()'declare -a PIPESTATUS='([0]="0")'declare -a test='([0]="a" [1]="b" [2]="c" [3]="d" [4]="e")'                        ------>定义成功

练习:生成10个随机数保存于数组中,并找出其最大值和最小值

#!/bin/bashdeclare -a randdeclare -i max=0declare -i min=32767for i in {0..9}do        rand[$i]=$RANDOM        echo ${rand[$i]}                if [ ${rand[$i]} -gt $max ] ;then                let max=${rand[$i]}        else                let min=${rand[$i]}        fidoneecho The max number is $maxecho The min number is $minunset i min max[root@centos6 app]# ./test.sh   1575426316314216902508713183206371952025223494The max number is 31421The min number is 23494

数组切片

格式:${ARRAY[@]:offset:number}
offset: 要跳过的元素个数
number: 要取出的元素个数

[root@centos6 app]# echo ${test[*]}a b c d e[root@centos6 app]# echo ${test[*]:2:3}       ------>跳过2个元素,取3个元素c d e

   以上所说的语法就是shell流程控制执行的一些基本用法了,每个语法单独使用,或者混合使用都是可以的,可以相互嵌套来实现自己的目的,虽然有时候逻辑关系会很强,但只要梳理清楚了还是很容易理解的,希望自己好好加油吧~~~