Shell编程

1 shell脚本运行

    所谓脚本,就是把众多命令写入一个文件中,让其按照一定的逻辑顺序执行,以完成一个具体的功能。而在Linux的shell编译环境下,shell编程与众多编程语言一样,也有其独立的语法。

1.1  脚本基本结构

首先,来看一下基本的shell语法格式:

vi  /mnt/test.pl   ---创建脚本文件,注:Linux中脚本一般为.pl或.sh后缀

   #!/bin/bash    ---指定编译本脚本的shell

   echo  hello      ---输入多条命令

   ls  -l  /var/

   echo  over  

以上就是一个脚本的最简单的案例,其中第一句的#!/bin/bash一般必须书写,至于后面的命令,可以根据个人需求自定义编写。

我们可以看到,脚本编写其实与创建文本文档一样,使用vi即可,编写完毕,它也就是一个文档而已,需要给它增加执行权限才可以当做脚本被执行:

chmod a+x  /mnt/test.pl

有了执行权限,可以直接使用绝对路径调用执行:

/mnt/test.pl    ---执行脚本

1.2  脚本执行方式

   /mnt/test.pl   ---指定绝对路径执行脚本

   或者

   cd  /mnt   ---进入脚本所在的目录

   ./test.pl   --- .表示当前目录

   注:本方式要求脚本必须有x权限才可被执行

1.3 自定义系统命令

若想让我们自己编写的脚本,像系统命令一样可以随时随地的执行,那么就要把脚本按照系统命令的原理操作。首先,我们知道,系统中的命令大多属于外部命令,执行时都是调用的其可执行程序,使用whereis和which可以查看得到。

那么,按照Linux的命令原理,手动输入的命令,都会去PATH环境变量指定的路径中去寻找命令对应的可执行程序,可以用echo$PATH查看得到

系统中所有命令的可执行程序,都是存放于这些路径内的。总结得到,我们可以借助于这种原理,把我们的脚本程序设置为系统命令。

假设有如下脚本:

vi  /mnt/cpuTest.pl

       #!/bin/bash

       echo start

       sar 1  1

       echo end

   chmod  a+x /mnt/cpuTest.pl

将其设置为系统命令的方式有如下两种:

方式一:

       cp /mnt/cpuTest.pl   /usr/bin/    ---复制到系统命令的所在目录下

       缺点:自定义脚本与系统命令不分离,难以区分,扰乱原有的系统命令;不便于管理和查找。

   方式二:  常用

       PATH="$PATH:/mnt"    ---在PATH变量后追加上脚本所在的目录

   注:若想让对PATH的设置永久生效,则需要把该命令写入到环境变量配置文件中才可以;脚本名尽量不要与系统中已存在的命令名重复。

2 shell编程

以上介绍了脚本的运行,下面来讲解以下具体的编程

2.1 变量

关于变量,是所有开发语言必不可少的运行工具,shell编程也不例外。先来解释一下变量的定义:程序运行过程中,用于临时存放数据的一块内存空间即是变量,给这块空间起个名字,即变量名(此定义虽非官方,但很容易理解)。下面来看一下变量的声明、赋值。

shu=5  

以此代码为例,是声明了一个变量叫shu,即会在内存中开辟一块空间,给shu专用。=5表示给变量存入数据,即存到内存中,称为 赋值。亦或:name=zhang  也是声明并赋值。

变量的使用也同样是用$符加以提取,如下例:

echo  "my name is: $name"   ---用$提取变量的值,加以使用

另外,当使用变量时,若变量名与之后文件接连书写,没有空格,会造成变量名的识别错误,如:echo $shua,则shell会认为要输出变量shua的值,但如果我们只声明了变量shu,且想要输出变量shu的值呢?可参看下例解决:

shu=5

echo  ${shu}a   ---用{}明确变量名,则输出结果:5a

再来看一下变量的计算,先看如下案例:


可见,代码运行的结果并非我们想象的求和的结果,而是shu3=5+3。这是因为变量赋值时,默认所有数据都当字符类型处理,所以shu1、shu2其实赋值的是字符形态的3、5,所以赋值给shu3时其实仅相当于让三个字符串联而已。

那么,若要想让计算式按数学运算的方式执行,需要使用let关键字,如下例:

shu1=3  

shu2=5 

let  shu3=$shu1+$shu2    ---let开头的代码,将以数学计算的方式执行

echo  shu3=$shu3      ---此时输出结果为:shu3=8

还有,变量的赋值,也有其他方式。之上的案例中我们可以看到都是在代码中直接给变量赋值的。其实,我们还可以要求人为的从键盘输入数据赋值给变量,如下:

read   shu  ---read表示:要求从键盘输入一个数据,赋值给变量

例如:

图中zhang是我们手动输入的姓名。

再者,我们还可以将命令的执行结果赋值给变量。案例格式如下:

   shiJian=`date +"20%y-%m-%d%H:%M:%S"`   

注:用反单引号把命令引起来,功能:把命令的执行结果赋值给变量

最后再来看一下变量值的截取,如下例:

shu=abc123

shu2=${shu%%1*}   ---%%表示去除右侧字符,*还是通配符,结果: shu2=abc

shu3=${shu##*c}   ---##表示去除左侧字符,结果:shu3=123

2.2 判断语句

说到判断语句,与众多编程语言思路一样,无非是给定一个条件,如果条件满足则执行对应的代码。那么在shell中的格式如下:

if [  条件 ];  then      --- 格式要求:[   ]; 符号左右必须有空格

//代码    

fi

运行逻辑:当条件满足、成立,则执行代码,否则不执行代码

虽然逻辑过程容易理解,但是关于条件的书写格式,是比较复杂的,常用格式如下:

   [  $shu -gt 20 ];   --- 判断比较数字   -gt 大于  -lt小于   -eq等于  -ge大于等于  -le小于等于 –ne不等于

   [  $name = "zhang"  ];  ---用 = 做字符判断,注:=左右无空格,表示赋值功能,=左右有空格,表示判断,!=表示判断不等于

   [  -f "/mnt/f1" ];     ---  -f 判断给定的路径是否是一个文件,即判断文件是否存在  -d 判断目录  -l 判断软链接

   [-x "/mnt/test.pl" ];   --- -x判断给定的文件是否有执行权限,-r 判断读权限  -w判断写权限

   [-z $shu ];     ---判断变量是否为空,即未赋值。若为空,则判断成立

   [  条件1  -a  条件2  ];  --- -a表示逻辑与  -o 逻辑或   !逻辑反,即取反值

注:关于满足、不满足,成立、不成立这种对立的判断,称为布尔(bool)型数据,只有两个结果,成立满足叫true , 不成立不满足叫false。

除了这种简单的判断语句,if还有两种格式,如下:

    格式2:

if [  条件 ]; then   ---如果条件满足,执行代码1,否则执行代码2

           //代码1

       else       ---否则,若条件不满足,则执行代码2

           //代码2

       fi

   格式3:

       if [  条件1 ]; then   ---如果条件1满足,执行代码1

           //代码1

      elif [ 条件2 ]; then  ---否则若条件1不满足,判断条件2                    //代码2

       elif [  条件3 ]; then    ---如果条件1、2都不满足,判断条件3

           //代码3

       else      ---若前面条件都不满足

           //代码4

       fi

好了有了if的三种格式,我们在编程时,就可以依据需求完成不同条件的判断了,来看下面案例:

echo please input your  age

read age

if [  $age -lt  16 ]; then

echo  child

elif [  $age -lt  30 ]; then

echo  younger

elif [  $age -lt  40 ]; then

   echo  stronger

elif [  $age -lt  50 ]; then

   echo  zhong nian

else

   echo  older

fi

以上案例中,根据年龄,逐级判断,输出年龄段状态。值得注意的是,我们排列的条件顺序是从年龄的小到大,那么当年龄大于16岁时回去判断是否小于30,一次类推。但如果我们把条件顺序反过了写,如下:

if [  $age -lt  50 ]; then

echo  uncle

elif [  $age -lt  40 ]; then

echo  stronger

elif [  $age -lt  30 ]; then

   echo  younger

elif [  $age -lt  16 ]; then

   echo  child

else

   echo  older

fi

则我们可以想象到,假设当age=15时,第一个条件小于50的判断是满足的,那么就会直接输出 uncle了,就与我们原先设想的结果完全不同。所以我们一定要先明确一点:只有在前面的条件不满足时,才会去判断后面的条件。在编写多级判断语句时一定要注意判断条件的先后顺序。

好了,下面我们来展示一个综合案例,是一个自制计算器的小程序,大家可以看明白思路后,自行编写试试:

#!/bin/bash

echo"------------------------------"

echo "   welcome to my  calc"

echo"------------------------------"

echo "start"

echo please input the first  num:

read n1

echo please input the second  num:

read n2

echo "please input the fu:+  - *  /  %"       

read fu

if [  "$fu" = "+" ]; then    

#注:因为fu可能会是*,*又表示通配符概念,所以用""还原回标准字符状态,   #就不具备特殊符号的意义了

   let  res=$n1+$n2

elif [  "$fu" = "-"]; then            

   let  res=$n1-$n2

elif [  "$fu" = "*"]; then            

   let  res=$n1*$n2

elif [  "$fu" = "/"]; then            

   let  res=$n1/$n2

elif [  "$fu" = "%"]; then            

   let  res=$n1%$n2

fi

echo "$n1 $fu $n2 = $res"    #输出最终的计算式,如:1 + 2 = 3

2.3  多分支语句

   与if…elif…elif…else…fi 类似,shell中还有一个可以实现多层判断的语句,就是case多分支语句。下面是它的格式与思路

    case  $变量  in    ---执行逻辑:根据变量的值,找到下面对应的项,执行代码

       值1)   代码1  ;;   --- ;; 两个分号,表示本项代码的结束

       值2)   代码2  ;;

       值3)   代码3  ;;

       *)   代码4  ;;   --- * 项表示,变量没有对应的值,则执行*这一项的代码

    esac

例如:

    echo  "请输入考试名次:"

        read mingCi

        case $mingCi  in

       "1") echo "第一名奖励200元" ;;

       "2") echo "第二名奖励100元" ;;

       "3") echo "第三名奖励50元" ;;

       "*") echo "无奖励" ;;

        esac

需要介绍的是,case虽然书写简练,并且也具备多级判断的功能,但是只能做变量值的等值判断,但if…elif语句可以实现变量在区间值(如分数范围,年龄范围等)的判断,所以各有所长,在具体编程时应该在不同时机选择合适的语句。

2.4  循环语句

说到循环语句,各种开发语言中都有,shell中用的开发语句主要以while为主。循环语句的功能是:让计算机重复性多次执行某块代码。来看一下while的语法格式:

while [ 条件 ];

do

   //代码

done

执行过程:条件判断=>执行代码=>条件判断=>执行代码=>...=>直到条件不满足,所以while语句是先判断,后执行的。

循环语句看似简单,但它的代码执行过程对初学者来说是需要逐步、逐次的思考清楚的,首先来分析一下如下案例:

例:输出100遍hello

   shu=1

   while  [ $shu  -le 100 ];

   do

       echo No.$shu  hello

       let shu=$shu+1

   done

分析以上案例执行过程,变量shu的初始值为1,第一次进入while,先判断shu是否小于等于100,结果为true,那么执行代码,输出一次hell,然后shu自我增加一次(取出shu的值,加1后再赋值给shu)得到shu的值为2,到这里第一次循环结束.然后再次返回判断部分,shu值为2,小于等于100,判断成立,再次进入代码,以此类推。综上,我们可以总结到,循环中必备的有四个内容,我们称为循环四要素。

循环四要素:初值  条件  循环体(即代码)   自更新

有了四要素后,我们写完的代码,可以检查一下是否正确,要避免避免:无循环、死循环的现象。PS:无循环就是第一次条件不满足,直接跳过循环。死循环是循环内没有更新语句,造成判断条件永远成立,致使代码运行到循环后,不再停止、跳出。

好了,再来展示两个案例,以帮助大家理解循环:

例:计算1-100之间各数累加和

   shu=1

   sum=0

   while  [ $shu -le 100 ];

   do

       let sum=$sum+$shu

       let shu=$shu+1

  done

  echo  $sum

   以上案例的思路是按照累加的过程:1+2=3,3+3=6,6+4=10…,所以每次循环累加后,把和存入sum变量,下次循环再次累加。

例:求1-100之间3的倍数之和

   shu=1

   sum=0

   while  [  $shu-le 100 ];

   do

      let yu=$shu%3

      if [ $yu -eq 0 ];  then

       let  sum=$sum+$shu

      fi

      let shu=$shu+1

   done

   echo  $sum

以上两个案例,读者可以逐一研究代码的执行过程,以理解循环的功能。

再有,循环中还有两个循环控制语句:continue和break,功能如下:

continue  停止本次循环,跳入下一次循环

   break     停止、跳出整个循环

案例如下:

    例:求1-100之间3的倍数之和

       shu=1

       sum=0

       while  [  $shu-le 100 ];

       do

           let yu=$shu%3   # %模运算,即求余数的运算

           if [ $yu -ne 0 ];  then

              let shu=$shu+1

              continue

           fi

           let sum=$sum+$shu

           let shu=$shu+1

       done

       echo $sum

   案例中可看到,判断中当shu除以3的余数不为0时,即不是3的倍数,将会进入if语句,自加后执行continue语句,则跳出当前循环,直接进入到了下一次循环判断了,那么后面的累加和操作将不再执行。

   例:计算1-100之间各数的累加和,求累加到哪个数时,和到达1000

       shu=1

       sum=0

       while [  $shu =le 100 ];

       do

       letsum=$sum+$shu

       if[ $sum -ge 1000 ]; then

           echo $shu

           break

       fi

       let  shu=$shu+1

       done

       echo $shu

本案例中,当累加和到达1000时,就没有必要继续循环了,所以直接break停止了循环.

    以上的所有案例,我们看到都是有固定循环次数的,其实while也可以支持没有固定次数的循环操作,如下例:

    jiXu="y"; # 为了满足第一次循环,赋初值为y

    while  [ $jixu = "y" ];

    do

       echo  "上午上课"

       echo  "下午实验"

       echo  "晚上自习"

       echo  "明天继续吗?y/n"

       read jiXu

    done

另外,shell编程还有for语句结构的循环,它的语法如下:

        for  变量  in  值1  值2  值3  ...        do

            //代码

done

执行思路:用给定的值,逐一赋值给变量,带入代码执行

缺点:不支持数据范围的指定,如:1-100。PS:若要设定范围需要内嵌特殊代码。

案例:例:计算1-10之间各数累加和

       sum=0

       for shu  in  1 2 3 4 5 6 7 8 9 10

       do

          letsum=$sum+$shu

       done

      echo $sum

2.5 选择语句结构

shell中,还有一个独特的语句结构,就是选择结构,这个结构在java、C语言中是没有的,下面来看一下它的语法格式:

select 变量   in  值1  值2  值3 ...  

do

     //代码

     break  ---停止,跳出select结构,若不加break句,会循环重复选择

 done

 执行思路:把列举的值当做菜单以供选择,根据用户选择,把对应的值赋值给变量,带入代码。

例:

   select  xuan in  aaa  bbb ccc  ddd

   do

       echo your choice is : $xuan

       break

   done

执行过程如下图:

若没有break语句,则执行过程如下:


如上图,我们只能通过ctrl+c组合键关闭shell进程。

3 组合应用

首先,先来看一下变量赋值的一个应用:


图中可见,显示f1中第三列文字,赋值给变量words后,显示变量值时是不分行的,也就说明:当命令结果是多行状态时,赋值给变量后,将变为一行数据,即变量的值中不支持回行。

注:若想在输入命令时,让系统以shell程序的方式执行,则把多行代码用;分隔开即可。

然后,我们再来看一下read读取文档的使用:

   read  hang <  /mnt/f1   ---读取文档中的第一行文字,赋值给变量

但是这个read命令只能读取第一行文字,再次执行还是第一行。原因是因为访问文件时会打开文件,创建文件流,会有指针读取文件的第一行文字,若再次读取,则指针会下移一行,做读取。但是用这个命令时,打开文件,读取一行后立即关闭了文件。再次执行命令,又重新打开了文件,又从第一行开始读取了,所以无法实现多行读取功能。PS:以上原因有过开发经验的读者会比较好理解,虽然不甚准确,但思路接近,比较容易理解,适合于初学者。

那么如果想要读取文件中的每一行文字呢?则需配合while循环来使用,看下例:

   shu=1

   while  read  hang       

do

       echo No.$shu: $hang

       let shu=$shu+1

   done < /mnt/f1

案例功能:逐行读取文档内容,每次读取出一行,赋值给变量,带入代码。

用while配合read使用,则读取完一行后不会关闭文件,进而就可以使指针下移一行,再次读取第二行了。需要解释的是,当read读取成功后,即等于读取操作结果为true,正适合于while的判断;而当读取完文件的最后一行后,再次读取将读取失败,则视为false的结果,所以while循环将停止。运行结果如下图:


好了,在案例中我们也可以看到文件f1原有内容类似于表格,是多行多列的内容,那么我们也可以对每行内容中的每列文件加以单独提取,案例如下:

shu=1

while read  c1  c2 c3  

do

   echo  No.$shu: $c3

   let  shu=$shu+1

done < f1

代码功能:逐行读取文档内容,每次读取出一行,把该行各列的文字,赋值给对应的变量,带入代码,代码中c1 c2 c3是三个变量,对应文件中每行的各列。

4 函数调用
4.1 函数的定义、调用

当我们需要以一段代码需要多次使用时,如果每次使用都要写一遍代码的话,那么又麻烦,代码又繁琐,那么可以使用函数来实现一次定义,多次使用。

函数,即是一段完整的代码,能够实现一个较小的功能,可以被shell程序所调用。格式如下:

定义格式: 

 function 函数名() {

           代码

       }

       或

       函数名 (){    ---不写function关键字也可以

           代码

       }

调用函数:

   shell代码中,直接写函数名,即可调用。

案例:

   vi  test.sh

   #!/bin/bash

   function  qiuHe(){    

       shu=1

       sum=0

       while [ $shu -le 100 ];

       do

           let sum=$sum+$shu

           let shu=$shu+1

           done

       echo $sum

   }

   echo  "我们将要计算1-100之间各数的累加和,结果如下:"

   qiuHe    #调用函数qiuHe

值得注意的是:(1)在shell脚本中,程序的开始运行点,并不会从函数开始,而是从函数之外的第一行代码开始执行,所以上例中运行的第一条代码是echo "我们将…"句。(2)还有shell的代码执行过程是由上往下读取到一条语句,即编译一条,所以在函数的编写时,函数的定义语句必须写在调用语句之前,否则函数将无法使用。(3)与其他开发语言不同,shell中的变量并没有严格的生存期概念,只要在之前代码出现使用过的变量,在之后代码中都可以直接使用。

4.2 函数的参数传递

当我们调用函数时,如果函数要用到某些数据而自己无法得到,则需要调用方为它提供,这就可以使用参数传递实现。所以参数传递的功能是:调用方,给函数传递素材性数据,让函数使用该素材数据做运算,该素材数据称为参数。

函数中参数定义的格式是:在函数代码中用  $数字  的格式来指定参数的编号、个数,如:$1  $2,若达到10个以上的参数时需用{}明确,如:${10}。调用函数时,只需要在函数名后面列举出要传递进去的数据即可,如下例:

vi test.sh

   #!/bin/bash

   jiaFa(){

       letres=$1+$2   #使用参数,进行计算,参数与调用方给定的一一对应

       echores=$res

   }

   shu1=5

   shu2=10

   jiaFa  shu1 shu2     #调用函数,并在后面列举出传给它的参数

4.3 函数的返回值

反过来想,当函数执行完毕后,如果需要携带数据回到调用方,让调用方使用该数据继续运行,则使用函数的返回值实现。

函数中的书写格式是:在函数代码中用 return 关键字指定带回的返回值,调用方使用 $? 的格式接收返回值。案例如下:

vi test.sh

    #!/bin/bash

    jiaFa(){

       letres=$1+$2

       return  $res

    }

    shu1=5

    shu2=10

    jiaFa shu1  shu2     #调用函数,并在后面列举出传给它的参数

    he=$?   #$?代表之前代码中离的最近的一个函数的返回值

    echo $shu1 + $shu2 = $he 

4.4 小结

    通过以上的几个案例可以想到,当一段代码会经常被使用到时,我们可以提前把代码写到一个函数中,那么在之后的shell程序中,如果用到,只需要直接调用就可以了,无需再把代码编写一般,这样就实现了一次定义,多次调用的效果,既节约了代码,又清晰了思路。

另外,关于shell编程部分,初学者可能会感觉有些难度,那么首先要确保能够先理解本章中各案例的每行代码的功能,理解每个案例的执行思路。然后按照每个案例的功能,给自己设计一个类似的案例编写下试试,慢慢积累编程的思路和感觉。关于编程能力的锻炼是需要较多案例演习才能够掌握熟练的。这里为大家提供一个系统用户管理的完整案例以供大家借鉴。

vi  /mnt/userManage.sh

#!/bin/bash

echo"-------------------------------------"

echo "   welcome to user manage system"

echo"-------------------------------------"

echo ""

run=true

while $run   

do

   select  xuan  in "show all users" "add a new user" "change a user's password" "delete a user" "Exit"

   do

       case  $xuan  in

          "showall users")

               allUsers=`awk-F ":"  '{print $1}'  /etc/passwd`

                echo  $allUsers ;;

          "adda new user")

              echo please input a new username:

              read name

              useradd $name

              passwd $name  ;;

          "changea user's password")

              echo please input a  username forchange:

              read name

              passwd $name  ;;

          "deletea user")

              echo please input a  username fordelete:

              read name

              userdel -r $name  ;;

          "Exit")

              echo byebye

              run=false   ;;

       esac

       break

   done

done

  • 14
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值