我的上一篇博客写到我已经详细的介绍了SHELL脚本编程的循环与分支的相关内容,这些循环与分支在bash编程中有着至关重要的地位。今天我将要介绍bash编程进阶的第二部分,也是实际使用中很重要的一部分,函数和数组,但for的特殊用法没有介绍。因此,从for的特殊用法开始。

一、for的特殊用法

      for的特殊用法其实是多了双小括号的用法,即"((......))"格式,也可以用于算术运算。双小括号实际上与C语言的变量操作相同,即是使bash Shell实现C语言风格的变量操作,使for循环操作变得更简单。

for循环的特殊格式:

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

更清晰的可以从下图中看出:

spacer.gif1204916-20170916115638000-1850529456.png

控制变量初始化:仅在运行到循环代码段时执行一次

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

示例:

        编写脚本test.sh,使用for循环的特殊用法实现1~100的和。

#!/bin/bashfor ((i=1;i<=100;i++));do    let sum+=i doneecho sum=$sum

结果如下:

1.png

注:正确答案应该是5050。

二、select循环

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

        select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本,也可以按ctrl+c 退出循环。select 经常和 case 联合使用,与 for 循环类似,可以省略 in list,此时使用位置参量。

select循环的格式:

select variable in list        do              循环体命令       done

例:

       编写脚本,使用select循环实现一个简单的菜单。

#!/bin/bashPS3="请问你要吃什么(输入6退出):"select menu in 黄焖鸡 鱼香肉丝盖饭 烤肉饭 老碗面 招牌五谷渔粉 退出;do    case $REPLY in     1)        echo "黄焖鸡 15元";;    2)         echo "鱼香肉丝盖饭 15元";;    3)        echo "烤肉饭 12元";;    4)         echo "老碗面 9元";;    5)        echo "招牌五谷渔粉  12元";;    6)        break    esacdone

结果如下:

{MFW@4)CB~WFHNIQ]VPWZSD.png

三、信号捕捉trap

       trap是一个shell内建命令,它用来在脚本中指定信号如何处理。比如,按Ctrl+C会使脚本终止执行,实际上系统发送了SIGINT信号给脚本进程,SIGINT信号的默认处理方式就是退出程序。如果要在Ctrl+C不退出程序,那么就得使用trap命令来指定一下SIGINT的处理方式了。

       trap命令不仅仅处理Linux信号,还能对脚本退出(EXIT)、调试(DEBUG)、错误(ERR)、返回(RETURN)等情况指定处理方式。

trap的格式如下:

trap '触发指令' 信号:自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作trap '' 信号:忽略信号的操作trap '-' 信号:恢复原信号的操作trap -p:列出自定义信号操作,即提示当前使用的trap操作是什么。

示例:

        打印0-9,Ctrl+C不退出程序

#!/bin/bash trap 'echo "signal:SIGINT"' SIGINT trap -p for((i=0;i<=10;i++)) do     sleep 1     echo $i done

结果如下:

({​{F{Q8T5K_J~WEI~7OCGSA.png

四、函数

     函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分,函数和shell程序比较相似,区别在于:Shell程序在子Shell中运行,而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改。函数变量默认的是全局变量,所以要定义本地变量只影响函数内部,避免修改函数外的变量值。函数不能用exit退出,因为函数本身没有开启子进程会退出整个脚本。

【函数带参数的说明】

1:函数体中位置参数($1、$2、$3、$4、$5、$#、$*、$?以及$@)都可以是函数的参数
2:父脚本的参数则临时地被函数参数所掩盖或隐藏
3:$0比较特殊,它仍然是父脚本的名称
4:当函数完成时,原来的命令行参数会恢复
5:在shell函数里面,return命令的功能的工作方式与exit相同,用于跳出函数
6:在shell函数体里使用exit会终止整个shell脚本
7:return语句会返回一个退出值给调用的程序

4.1 定义函数

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

help function

语法一:  f_name (){    ...函数体...  }  语法二:  function f_name {  ...函数体...  }  语法三:  function f_name () {  ...函数体...  }

4.2 函数使用

函数的定义和使用:

 可在交互式环境下定义函数

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

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

调用:函数只有被调用才会执行

函数的调用若是在同一个脚本中,调用操作需要在定义的函数后面。

 调用:给定函数名

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

函数的生命周期:被调用时创建,返回时终止

4.3 函数返回值

函数有两种返回值:

  函数的执行结果返回值:

    (1) 使用echo等命令进行输出

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

  函数的退出状态码:

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

    (2) 自定义退出状态码,其格式为:

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

    return 0 无错误返回。

    return 1-255 有错误返回

4.4 交互式环境下定义和使用函数

定义该函数后,若在$后面键入dir,其显示结果同ls -l的作用相同

  dir

该dir函数将一直保留到用户从系统退出,或执行了如下所示的unset命令

  unset dir

4.5 在脚本中定义及使用函数

  函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用

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

示例:

        编写脚本funcl.sh,实现

#!/bin/bash# func1hello(){echo "Hello there today's date is `date +%F`"}echo "now going to the function hello"helloecho "back from the function"

结果如下:

4.png

4.6 使用函数文件

     可以将经常使用的函数存入函数文件,然后将函数文件载入shell,文件名可任意选取,但最好与相关任务有某种联系。一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数;若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件。

4.7 载入函数

函数文件已创建好后,要将它载入shell

定位函数文件并载入shell的格式:

 . filename 或 source filename

注意:此即<点> <空格> <文件名>,这里的文件名要带正确路径

4.8 执行or删除shell函数

要执行函数,简单地键入函数名即可,现在对函数做一些改动后,需要先删除函数,使其对shell不可用。使用unset命令完成删除函数

命令格式为:

unset function_name

4.9 环境函数

使子进程也可使用

声明:export -f function_name查看:export -f 或 declare -xf

4.10 函数参数

函数可以接受参数:

       传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“testfunc arg1 arg2 ...”

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

4.11 函数变量

变量作用域:

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

      本地变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数

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

注意:如果函数中有局部变量,如果其名称同本地变量,使用局部变量

在函数中定义局部变量的方法:

local NAME=VALUE

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的。

declare [选项] 变量名-r 声明或显示只读变量-i 将变量定义为整型数-a 将变量定义为数组-A 将变量定义为关联数组-f 显示此脚本前定义过的所有函数名及其内容-F 仅显示此脚本前定义过的所有函数名-x 声明或显示环境变量和函数-l 声明变量为小写字母 declare –l var=UPPER-u 声明变量为大写字母 declare –u var=lower

4.12 函数递归示例

函数递归:
       函数直接或间接调用自身,注意递归层数
递归示例:
      阶乘是基斯顿·卡曼于 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)!

练习

       1、编写函数,实现OS的版本判断

#!/bin/bashos(){        sed -r 's/.*[[:space:]]([0-9])\..*/\1/' /etc/redhat-release}os

结果如下:

os.png

      2、编写函数,实现取出当前系统eth0的IP地址

#!/bin/basheth0ip(){         ifconfig eth0 |grep Mask |tr -s ' '|cut -d' ' -f3 |cut -d: -f2}eth0ip

结果如下:

4JFSM2OXTV45W8XKZSYG@FY.png

       3、使用函数功能打印国际棋盘

#!/bin/bashred(){        echo -e "\033[41m    \033[0m\c"}yel(){        echo -e "\033[43m    \033[0m\c"}redyel() {        for ((i=1;i<=2;i++));do                for ((j=1;j<=4;j++));do                        [ "$1" = "-r" ] && { yel;red; } || { red;yel; }                done                echo        done}for ((line=1;line<=8;line++));do        [ $[$line%2] -eq 0 ] && redyel || redyel -rdone

结果如下:

qipan.png

       4、斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2),利用函数,求n阶斐波那契数列。

read -p "请输入斐波那契数列的阶数:" nfact(){        if [ $1 -eq 0 ];then                echo 0        elif [ $1 -eq 1 ];then                echo 1        else                echo $[`fact $[$1-1]`+`fact $[$1-1]`]        fi}        for i in `seq 0 $n`;do                fact $i        done

结果如下:

tuzi.png

五、fork×××

     fork×××是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源

函数实现

       :(){ :|:& };:    bomb() { bomb | bomb & }; bomb

脚本实现

    cat Bomb.sh     #!/bin/bash    ./$0|./$0&

六、数组

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

数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
         索引:编号从0开始,属于数值索引
注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持bash的数组支持稀疏格式(索引不连续)
声明数组:

declare -a ARRAY_NAMEdeclare -A ARRAY_NAME: 关联数组

注意:两者不可相互转换

6.1 数组赋值

数组元素的赋值:
(1) 一次只赋值一个元素;

ARRAY_NAME[INDEX]=VALUEweekdays[0]="Sundayweekdays[4]="Thursday

(2) 一次赋值全部元素:

ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

(3) 只赋值特定元素:

ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)

(4) 交互式数组值对赋值

read -a ARRAY显示所有数组:declare -a

6.2 引用数组

引用数组元素:

${ARRAY_NAME[INDEX]}

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

引用数组所有元素:

${ARRAY_NAME[*]}${ARRAY_NAME[@]}

数组的长度

(数组中元素的个数):

${#ARRAY_NAME[*]}${#ARRAY_NAME[@]}

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

unset ARRAY[INDEX]

删除整个数组:

unset ARRAY

6.3 数组数据处理

引用数组中的元素:
       数组切片:

 ${ARRAY[@]:offset:number}

offset: 要跳过的元素个数

number: 要取出的元素个数
取偏移量之后的所有元素

${ARRAY[@]:offset}

向数组中追加元素:

ARRAY[${#ARRAY[*]}]=value

关联数组:

declare -A ARRAY_NAME ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)

注意:关联数组必须先声明再调用

练习

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

#!/bin/bashdeclare -a randdeclare -i max=0declare -i min=32767for i in {0..9}; do          rand[$i]=$RANDOM          echo ${rand[$i]}          [ ${rand[$i]} -gt $max ] && max=${rand[$i]}          [ ${rand[$i]} -lt $min ] && min=${rand[$i]}doneecho "Max: $max Min:$min"

结果如下:

@SIJJAIUU2FQV1J]~X05]8V.png

2、编写脚本,定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的

文件;要统计其下标为偶数的文件中的行数之和

#!/bin/bashdeclare -a filesfiles=(/var/log/*.log)declare -i lines=0for i in $(seq 0 $[${#files[*]}-1]); do      if [ $[$i%2] -eq 0 ];then             let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1) fidoneecho "Lines: $lines."

统计并排序/var/log目录下所有以.log结尾的文件结果如下:

%JLTO4`5QF`{P`[K_LD)DVX.png

结果如下:

lines.png

3、输入若干个数值存入数组中,采用冒泡算法进行升序或降序排序

#!/bin/bashquit() {if [[ $1 =~ [Qq][Uu][Ii][Tt]|[Qq] ]];then    exit 0fi}read -p "请输入数字: " -a numquit $numfor x in ${num[*]};do    if [[ $x =~ ^-?[0-9]+$ ]];then        true    else        echo "请输入正确数字" 1>&2        exit 1    fidonewhile true;dofor ((i=0;i<${#num[*]};i++));do    for ((j=0;j<${#num[*]}-1;j++));do        if [ ${num[j]} -gt ${num[j+1]} ];then            n=${num[j]}            num[j]=${num[j+1]}            num[j+1]=$n        fi    donedoneecho ${num[*]}read -p "是否要继续输入数字进行冒泡,若输入quit则退出: "  num1quit $num1if [[ $x =~ ^-?[0-9]+$ ]];then    trueelse    echo "请输入正确数字" 1>&2    exit 2finum[${#num[*]}]=$num1done

结果如下:

miaopao.png