shell脚本高阶(二)

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

一、for的特殊用法

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

for循环的特殊格式:

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

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

1204916-20170916115638000-1850529456.png

控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

示例:

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

#!/bin/bash
for ((i=1;i<=100;i++));do
    let sum+=i 
done
echo 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/bash
PS3="请问你要吃什么(输入6退出):"
select menu in 黄焖鸡 鱼香肉丝盖饭 烤肉饭 老碗面 招牌五谷渔粉 退出;do
    case $REPLY in 
    1)
        echo "黄焖鸡 15元";;
    2) 
        echo "鱼香肉丝盖饭 15元";;
    3)
        echo "烤肉饭 12元";;
    4) 
        echo "老碗面 9元";;
    5)
        echo "招牌五谷渔粉";;
    6)
        break
    esac
done

结果如下:

三、信号捕捉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
# func1
hello()
{
echo "Hello there today's date is `date +%F`"
}
echo "now going to the function hello"
hello
echo "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

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/bash
os(){
        sed -r 's/.*[[:space:]]([0-9])\..*/\1/' /etc/redhat-release
}
os

结果如下:

os.png

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

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

结果如下:

4JFSM2OXTV45W8XKZSYG@FY.png

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

#!/bin/bash
red(){
        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 -r
done

结果如下:

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 "请输入斐波那契数列的阶数:" n
fact(){
        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_NAME
declare -A ARRAY_NAME: 关联数组

注意:两者不可相互转换

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

ARRAY_NAME[INDEX]=VALUE
weekdays[0]="Sunday
weekdays[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/bash
declare -a rand
declare -i max=0
declare -i min=32767
for 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]}
done
echo "Max: $max Min:$min"

结果如下:

@SIJJAIUU2FQV1J]~X05]8V.png

2、编写脚本,定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的
文件;要统计其下标为偶数的文件中的行数之和

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

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

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

结果如下:

lines.png

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

#!/bin/bash
quit() {
if [[ $1 =~ [Qq][Uu][Ii][Tt]|[Qq] ]];then
    exit 0
fi
}
read -p "请输入数字: " -a num
quit $num
for x in ${num[*]};do
    if [[ $x =~ ^-?[0-9]+$ ]];then
        true
    else
        echo "请输入正确数字" 1>&2
        exit 1
    fi
done
while true;do
for ((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
    done
done
echo ${num[*]}
read -p "是否要继续输入数字进行冒泡,若输入quit则退出: "  num1
quit $num1
if [[ $x =~ ^-?[0-9]+$ ]];then
    true
else
    echo "请输入正确数字" 1>&2
    exit 2
fi
num[${#num[*]}]=$num1
done

结果如下:

miaopao.png

猜你喜欢

转载自blog.51cto.com/12948961/2161449
今日推荐