更多内容请点击:

Linux学习从入门到打死也不放弃,完全笔记整理(持续更新,求收藏,求点赞~~~~) 

https://blog.51cto.com/13683480/2095439


第14章,Shell脚本编程进阶


本章内容:

               条件判断

               循环

               信号捕捉

               函数

               数组

               高级字符串操作

               高级变量

               Expect

 

过程式编程语言执行方式:

               顺序执行,选择执行,循环执行

               

条件选择-----------------------------------------------------------------------

 

if语句:

               结构:     可嵌套

               单分支:

                            if 判断条件;then

                                    条件判断为真的执行代码

                             fi

               双分支:

                            if 判断条件;then

                                    条件判断为真的执行代码

                                    (此段若想为空,使用":")

                             else

                                    条件判断为假的执行代码

                             fi

               多分支:

                            if 条件判断1;then

                                   cmd..

                            elif  条件判断2;then

                                   cmd..

                            elif  条件判断3;then

                                   cmd..

                             else

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

                             fi

        

case语句:

               格式:

                            case  变量引用 in

                            pat1)

                                           分支1

                                           ;;

                            pat2)

                                           分支2

                                           ;;

                             ...

                            *)

                                           默认分支

                                           ;;

                             esac

 

               注意:     case支持glob风格的通配符

                                   *             任意长度任意字符

                                   ?           任意单个字符

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

                                   a|b          a或b

                    

 

循环执行------------------------------------------------------------------------

               将某代码段重复多次运行

               重复运行多少次

                            循环次数事先已知

                            循环次数事先未知

               有进入条件和退出条件

               for,while,until

        

for循环

               格式:     

                            for 变量名 in 列表;do

                                    循环体代码

                             done

               机制:

                             依次将列表中的元素赋值给"变量名"

                             每次赋值后即执行一次循环体,直到列表中的元素耗尽,循环结束

               列表生成方式:

                            1     直接给出列表

                            2     整数列表

                                          {1..10..2}

                                          $(seq  1 2 10)

                            3     返回列表的命令

                                           $(cmd)

                            4     使用glob,如:*.sh

                            5     变量引用

                                           $@,$*

                             

while循环

               格式:

                            while CONDITION;do

                                    循环体

                             done

               CONDITION :

                            循环控制条件

                             进入循环之前,先做一次判断

                             每一次循环之后会再次做判断

                            条件为true,则执行一次循环,直到条件测试状态为false 终止循环

               因此:     CONDITION一般应该有循环控制变量,而此变量的值会在循环体不断的被修正

               进入条件:CONDITION为true        

               退出条件:CONDITION为false

               

until循环

               格式:

                            until CONDITION;do

                                    循环体

                             done

               进入条件:CONDITION 为  false

               退出条件:CONDITION 为  true

               

循环控制语句:

               用于循环体中

               continue [N] 默认为1

                             用于循环体中,提前结束第N层的本轮循环,而直接进入下一轮判断

                            所在层为第1层

 

               break [N] 默认为1

                             用于循环体中,提前结束第N层循环,所在层为第1层

               

               shift [N] 默认为1

                            用于将参数列表list 左移指定的次数,默认1次

                            参数列表list 一旦被移动,最左端的那个参数就从列表中删除。

                             while循环遍历位置参数列表时,常用到shift

                             

创建无限循环:

               1     while true;do

                            循环体代码

                     done

                    

               2     until false;do

                            循环体

                     done

 

 

特殊用法:

               

        while 循环的特殊用法(遍历文件的每一行)

                                   while read  变量名;do

                                           循环体代码

                                   done <  /file

                     一次读取file 文件中的每一行,且将行赋值给变量

               

                     PS:   也可以使用   cmd |while read;do

                                                  循环体

                                           done

                      但是循环中的数组值貌似不能输出,即如在done之后echo 数组中的值为空 ,需要注意

        

        双小括号方法,即((....))格式,也可以用于算术运算

                     也可以是bash实现C语言风格的变量操作

                                    i=10

                                    ((i++))

        

        for 循环 的特殊格式:

                      for((控制变量初始化;条件判断表达式;控制标量的修正表达式));do

                            循环体代码

                     done

                    

                     如:       for ((i=1;i<=10;i++));do

                                          echo  $i

                                    done

                     控制变量初始化:

                                    仅在运行到循环代码段时执行一次

                     控制变量的修正表达式:

                                    条件判断表达式:进入循环先做一次条件判断,true则进入循环

                                    每轮循环结束会先进行控制变量的修正运算,而后在做条件判断

 

                    

select循环与菜单:

               格式:     

                            select  变量  in  列表;do

                                    循环体命令

                             done

                            select  循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示

                                           PS3提示符,等待用户输入

                             用户输入菜单列表上的某个数字,执行相应的命令

                             用户输入被保存在内置变量REPLY中

                     PS2:     多行重定向的提示符

                     PS3:     select的提示符    

                     REPLY     保存select的用户输入

                    

               select与case

                             select是个无限训话,因此要记住用break命令退出循环,或用exit命令终止脚本。

                            也可以按Ctrl+c退出循环

                            select  经常和case联合使用

                            与for循环类似,可以省略in list ,此时使用位置参量

               

trap 信号捕捉:

               trap ‘触发指令’信号

                             自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作

               trap ''信号

                            忽略信号的操作

               trap '-' 信号

                            恢复原信号的操作

               trap -p

                             列出自定义信号操作

               

               注意事项:

                            信号:    格式可以是 int INT sigint SIGINT 2   

                             信号捕捉之后,虽然不会立刻结束脚本,但是脚本当前运行的命令却会被终止

                            如:       trap 'echo trap a sig2'  2

                                          sleep  10000

                                          ping  192.168.0.1

                                    如果捕捉到2信号,不会退出脚本,但是sleep会被打断,而继续执行ping命令

                                    

函数:------------------------------------------------------------------------------

 

函数介绍:

                            函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程

                            它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,

                     而是shell程序的一部分

                            函数和shell程序区别在于:

                                           shell程序在子shell中运行

                                           而shell函数在当前shell中运行。因此在当前shell中,函数可以对shell变量

                                           进行修改

 

定义函数:

                             函数由两部分组成:函数名和函数体

                            help  function

                                    语法1:

                                                  f_name(){

                                                         函数体

                                                  }

                                    语法2:

                                                  function f_name()

                                                  {

                                                         函数体

                                                  }

                                    语法3:

                                                  function f_name{

                                                  函数体

                                                  }

                             可以交互式环境下定义函数,如:

                                           [root@centos6 ~/bin]$func_name1(){

                                          >  echo nihao

                                          >  echo wohenhao

                                          >  ping  -c1 -w1  192.168.0.1   

                                          >  }

                                           [root@centos6 ~/bin]$func_name1

                                           nihao

                                           wohenhao

                                           connect: Network is unreachable

                             可将函数放在脚本文件中作为它的一部分

                             可放在只包含函数的单独文件中

 

函数调用:

                             函数只有被调用才会执行

                             函数名出现的地方,会被自动替换为函数代码

                            函数的生命周期:

                                           被调用时创建,返回时终止

                             在脚本中用户前必须定义,因此应该将函数定义放在脚本开始部分,直至shell首次

                             发现它之后才能使用

                             调用函数仅使用其函数名即可

 

函数返回值:

                             函数由两种返回值:

                                           函数的执行结果返回值:

                                                         1     使用echo等命令进行输出

                                                         2     函数体中调用命令的输出结果

                                           函数的退出状态码:

                                                         1      默认取决于函数中执行的最后一条命令的退出状态码

                                                         2     自定义退出状态码,return命令

                                          return  :        从函数中返回,用最后状态命令决定返回值

                                                                return 0 无错误返回

                                                                return 1-255 有错误返回

                                    

删除函数:

                            unset  func_name1

 

函数文件:

               创建函数文件:

                            #!/bin/bash

                            # function.main

                            f_hello()

                             {

                                   echo  helio

                             }

                            f_func2(){

                                    ...

                             }

                             ...

               系统自带函数文件:/etc/rc.d/init.d/functions    

               

               使用函数文件:

                            可以将进程使用的函数存入函数文件,然后将函数文件载入shell

                             文件名可任意选取,但最好与相关任务有某种联系,如  function.main

                            一般函数文件载入shell,就可以在命令行或脚本中调用函数,

                            可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数

                             若要改动函数,首先用unset命令从shell中删除函数,改动完毕后,再重新载入此文件

 

               载入函数:

                             要想使用已创建好的函数文件,要将他载入shell

                            使用

                                   .  /path/filename

                                   source  /path/filename

                    

               如果使用source 载入函数之后,对函数文件的某个函数做了修改,需要unset函数之后重新载入

                            unset  func_name1

                            或者exit 重新登录然后再次source

               

               默认本shell进程有效,如需函数子进程有效,需声明

                            export -f func_name  

                            declare  -xf

               如需查看当前所有的全局函数

                            export -f  或 declare -xf

                     例如:

                            [root@centos6  ~/bin]$export -f func_release

                            [root@centos6  ~/bin]$export -f

                            func_release ()  

                            {  

                                   declare  rel=$(cat /etc/centos-release|tr -dc [0-9]|cut  -c1);

                                   echo  $rel

                             }

                            declare -fx  func_release

                            [root@centos6  ~/bin]$

                    

函数参数:

               函数可以接受参数:

                             调用函数时,在函数名后面以空白分隔给定参数列表即可;

                            例如 func_name arg1 arg2 ...

               在函数体当中,可以使用$1,$2..调用这些参数;还可以使用$@,$*,$# 等特殊变量

               注意区分脚本的位置参数和传递给函数的位置参数

               

函数变量:

               变量作用域:

                            环境变量:    当前shell和子shell有效

                            本地变量:    只在当前shell进程有效,为执行脚本会启动专用shell进程;

                                                  因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数

                            局部变量:     函数的生命周期;函数结束时变量会被自动销毁

               注意:     如函数中的变量名同本地变量,使用局部变量

               函数中定义局部变量:

                            local  name=

                            declare name=            declare自带局部变量属性

                            declare -ig  在函数中定义普通变量,centos6不支持

 

函数递归:

                             函数直接或间接调用自身

                            注意递归层数

               

               函数递归示例:

                     阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语

                     一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的

                     阶乘为1,自然数n的阶乘写作n!

                                   n!=1×2×3×...×n

                                    阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n

                                   n!=n(n-1)(n-2)...1

                                   n(n-1)! =  n(n-1)(n-2)!   

               示例:

                      fact.sh

                            #!/bin/bash

                            func_factorial()

                             {

                            if [ $1 = 0 ];then

                                          echo  1

                            else  

                                          echo  $[$1*`func_factorial $[$1-1]`]                                                   

                             fi

                             }

                            func_factorial  $1

               

fork×××:

                      fork×××是一种恶意程序,它的内部是一个不断fork进程的无限循环,实质是一个简单

                      的递归程序。由于程序是递归的,如果没有任何显示,这会导致整个简单的程序迅速

                     耗尽系统所有资源

        

               函数实现:

                            :(){  :|:&};: 

                            bomb(){ bomb|bomb&};bomb

               脚本实现:

                            cat  bomb.sh

                            #!/bin/bash

                            ./$0|./$0&

               多种语言版本             

 

数组---------------------------------------------------------------------------------

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

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

               数组名和索引:

                     索引:编号从0开始,属于数值索引

                      bash4.0版本之后,索引可以支持使用自定义的格式,而不仅是数值格式,即为关联索引

                      bash中的数组支持稀疏格式(索引不连续)

               声明数组:

                            declare -a array_name

                            declare -A  ARRAY_NAME 关联索引

                            彼此不可互相转化

                             

数组赋值:

               数组元素的赋值:

                     1     一次只赋值一个元素

                             array_name[index]=VALUE

                            如:

                                   weekdays[0]="sunday"

                                   weekdays[4]="thursday"

                     2     一次赋值全部元素

                            array_name=("VAL1" "val2" "val3"...)

                     3     只赋值特定元素

                            array_name=([0]=varl [3]=val2...)

                     4     交互式数组值对赋值

                            read -a array_name1

                            如:      

                                           [root@centos7 ~]$read -a  array_name1

                                          monday  tusday wensday thursday

                                           [root@centos7 ~]$echo  ${array_name1[@]}

                                          monday  tusday wensday thursday

               注意:

                     如果先赋值单个元素array[0]=a,

                            再使用赋值全部 array=(b c d) 或者特定赋值 array=([1]=e [2]=f  [3]=g)    

                            会使之前单个元素array[0]被覆盖消失

                             

                      索引数组可以无需声明直接赋值使用

                      关联数组必须先声明之后才能赋值使用

                                    如:[root@centos7 ~]$array3[0]=mage

                                           [root@centos7 ~]$array3[1]=zhangsir

                                           [root@centos7 ~]$echo  ${array3[*]}

                                          mage  zhangsir

                                           [root@centos7 ~]$echo  ${array3[1]}

                                           zhangsir

                                           [root@centos7 ~]$echo  ${array3[0]}

                                           mage

                                           [root@centos7 ~]$array4[ceo]=mage

                                           [root@centos7 ~]$array4[cto]=zhangsir

                                           [root@centos7 ~]$echo  ${array4[*]}

                                           zhangsir

                                    直接赋值使用关联数组会赋值失败,只显示最后一个值

                    

数组引用:

               引用数组元素:

                             ${array_name[index]}

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

               引用数组所有元素

                            ${array_name[@]}

                            ${array_name[*]}

               数组的长度(数组中元素的个数)

                            ${#array_name[*]}

                            ${#array_name[@]}

               删除数组中的某元素:导致稀疏格式

                            unset  array[index]

               删除整个数组

                            unset  array

                             

数组数据处理

               引用数组中的元素:

                            数组切片:${array[@]:offset:number}

                                   offset:     要跳过的元素个数

                                    number:要取出的元素个数

                             ${array[0]:offset}            取偏移量之后的所有元素

                            ${array[0]: -n}              取最后n个元素

                            ${array[0]::2}                 取开头2个元素

                            ${array[0]:  -m:-n}         跳过最后第n+1个到第m个元素间的所有元素

                             

               向数组中追加元素:

                            array[${#array[*]}]=

               

               关联数组:

                            declare -A  array_name

                             array_name=([idx_name1]=val1 [idx_name2]=val2  ...)

                             

 

字符串处理-------------------------------------------------------------------------

 

字符串切片:

               ${#var}:                         返回字符串变量var的长度

               ${var:offset}:                  返回字符串变量var中从第off个字符后(不包括第offset个字符)的字符

                                                         开始,到最后的部分,offer的取值在0到${#var}-1之间(bash4.2之后允许为负值)

               ${var:offset:number}:     返回字符串变量var中第off个之后的num个字符(不包含off)

               ${var: -n}:                            取字符串最右侧那个字符(冒号后需加一个空格)

               ${var: -n:-m}:                取倒数第m+1个字符 到 倒数第n个字符

               

基于模式取子串:

               ${var#*word}        其中var为变量名,不需要加$引用,word可以是指定的任意字符串

                     功能:自左而右,查找var变量所存储的字符串中,第一次出现的word,删除字符串开头

                     至第一次出现word字符之间的所有字符

               ${var##*word}

                      贪婪模式,删除字符串开头到最后一次”word“指定的字符之间的所有内容

                    

               ${var%word*}         其中word可以是指定的任意字符串

                     功能:    自右边而左,查找var变量所存储的字符串中,第一次出现word,删除字符串最后一个字符

                     向左至第一次出现word字符之间的所有字符

               ${var%%word*}        

                                    自右而左,删除至最后一个word所指定的字符串

                                    

查找替换:

               ${var/pattern/substr}:

                            查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之

               ${var//pattern/substr}:

                            替换所有能被pattern所匹配到的字符串,以substr替换

               ${var/#pattern/substr}

                            行首被pattern匹配,并替换

               ${var/%pattern/substr}:

                            行尾被pattern匹配,并替换

               

查找删除:

               ${var/pattern}:               删除第一次被pattern匹配到的字符串

               ${var//pattern}:       删除所有被pattern匹配到的字符串

               ${var/#pattern}:     删除pattern为行首所匹配到的字符串

               ${var/%pattern}:     删除pattern为行尾所匹配到的字符串

               

字符串大小写转换:

               ${var^^} 把var中所有的小写字母转换为大写

               ${var,,}    把var中所有的大写字母转换为小写

               

 

高级变量用法:-------------------------------------------------------------------

 

变量赋值:

               var=${str-expr}      str为变量,expr为字符串

                            如果str没有没配置,var=expr

                            如果str配置且为空,var=

                            如果str配置且非空,var=$str

               var=${str:-expr}    

                            如果str没有配置或者为空,var=expr

                            如果str已配置且非空:      var=$str

               其他诸多用法,此处不一一列举,如有需要查看相关表格查询

 

高级变量用法:有类型变量

               shell变量一般是无类型的,但是bash  shell提供了declare和同样typeset两个命令

               用于指定变量的类型,两个命令是等价的

 

declare:

               declare [option] [var]

                            -r           声明或显示只读变量

                            -i           整型数

                            -a          数组

                            -A          关联数组

                            -f           显示已定义的所有函数名及其内容

                            -F           仅显示已定义的所有函数名

                            -x          声明或显示环境变量和函数

                            -xf 全局函数

                            -l           声明变量为小写字母

                            -u          声明变量为大写字母

               declare -ig   在函数中定义普通变量,centos6不支持           

eval:

               eval命令将会首先扫描命令进行所有的置换,然后再执行该命令。该命令适用于那些一次

               扫描无法实现其功能的变量:该命令对变量进行两次扫描

               示例:    eval  echo {1..$i}

                    

 

间接变量引用:

               如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值

               就称为间接变量引用

               如:

                             var1=var2

                             var2=nnn

               bash 提供了两种格式实现间接变量引用

                            eval  var=\$$var1

                            var3=${!var1}

                     如:      

                                    var1=var2

                                    var2=nnn

                                   var3=${!var1} 或者 eval var3=\$$var1

                                   echo  $var3

                                    nnn

                    

创建临时文件:---------------------------------------------------------------------

mktemp :  

               创建并显示临时文件,可避免冲突

               mktemp [option]..[template]

                            template:       filenameXXX

                            -d          创建临时目录

                            -p DIR 或 --tmpdir=DIR     指明临时文件所存放目录位置

               示例:

                            tmpfile1=`mktemp  httptmp-XXX`

                            tmpdir=`mktemp -d  /tmp/roottmp-XXXX`

                            tmpdir=`mktemp  --tmpdir=/tmp roottmp-XXXX`

                             

 

安装复制文件:

install       命令:

                     install [options] source  dest       单文件

                     install [] source dir                    单文件

                     install [] -t dir source           单文件

                     install [] -d  dir            创建空目录

               选项:

                     -m mode 默认755

                     -o  owner

                     -g  group

               示例:

 

 

expect介绍-----------------------------------------------------------------------

               expect 是由Don Libes 基于Tcl(Tool Command Language)语言开发的

               主要应用于自动化交互式操作的场景,借助expect处理交互的命令,可以将交互过程

               如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台

               服务器执行相同操作的环境中,可以大大提供系统管理人员的工作效率

 

expect命令:

               expect [选项] [-c cmds] [[ -f[f|b]] cmdfile ]  [args]

               选项:

                     -c          从命令行执行expect脚本,默认expect是交互地执行的

                            示例:expect -c 'expect "hello" { send "you said  hello\n" }'

                     -d          可以输出调试信息

                            示例:expect -d ssh.exp

               

               expect中的相关命令

                            spawn           启动新的进程

                            send              用户向进程发送字符串

                            expect            从进程接受字符串

                            interact   允许用户交互

                            exp_continue 匹配多个字符串,在执行动作后加此命令

                             

               expect最常用的语法(tcl语言:模式-动作)

                             单一分支模式语法:

                                   expect "hi"  { send "you said hi \n"}

                                    匹配到hi后,会输出"you said  hi",并换行

                            多分支模式语法:

                                   expect "hi"  { send "You said hi\n" } \

                                   "hehe" { send "Hehe yourself\n" } \

                                   "bye" { send "Good bye\n" }

                                     匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:

                                   expect  {

                                          "hi"  { send "You said hi\n"}

                                          "hehe"  { send "Hehe yourself\n"}

                                          "bye"  { send "Good bye\n"}

                                   }                                   

expect 示例:

        ssh自动键入密码

                     #!/usr/bin/expect

                     spawn ssh  192.168.65.132

                     expect  {

                                   "yes/no"  { send "yes\n";exp_contunue }

                                   "password"  { send "112233\n"; }

                     }

                      interact

                     #expect  eof

               

        scp自动键入密码

                     #!/usr/bin/expect

                     spawn scp /etc/passwd  192.168.65.132:/data

                     expect  {

                                   "yes/no"  { send "yes\n";exp_continue }

                                   "password"  {send "112233\n" }

                     }

                     expect eof                   

        

        自动从文件获取ip地址,且登录ip地址机器的root账号,并创建账号

        也可以不用条用,直接在bash脚本中引用expect代码

                     cat ssh.exp  

                            #!/usr/bin/expect

                            set ip [lindex $argv  0]

                            set user [lindex  $argv 1]

                            set password [lindex  $argv 2]

                            spawn ssh  $user@$ip

                            expect  {

                                           "yes/no" { send "yes\n";exp_continue  }

                                           "password" { send "$password\n"  }

                             }

                            expect "]#" { send "useradd user1\n" }

                            expect "]#" { send "echo nagedu |passwd --stdin  user1\n"}

                            send  "exit\n"

                             #interact

                            expect eof            

                    

                     cat   sshauto.sh

                            #!/bin/bash

                            while read ip;do

                             user=root

                             password=112233

                            ssh.exp $ip $user  $password

                            done <  /root/bin/ip.txt

 

        自动从文件获取ip地址,并scp同一文件到所有主机的root目录.txt 

                     #!/bin/bash

                     declare  password=112233

                     while read ip;do

                                   expect  <

                                   spawn scp  /root/bin/scpauto.sh $ip:/root

                                   expect  {

                                                  "password" { send "112233\n"  }

                                    }

                     expect  eof

                     EOF

                     done <  /root/bin/ip.txt

 

 

笔记整理完成时间:2018年5月16日20:21:12