Shell中函数、字符处理、数组

函数及中断控制

函数:在Shell脚本中,将一些需重复使用的操作,定义为公共的语句块,即可称为函数。作用:通过使用函数,可以使脚本代码更加简洁,增强易读性

      提高Shell脚本的执行效率(避免代码重复)

服务脚本中的函数应用:适用于比较复杂的启动/终止控制操作

  方便在需要时多次调用

先声明函数,再调用函数

1)函数的定义方法

格式1:                                  格式2:

    function  函数名 {                        函数名() {

        命令序列                              命令序列

        .. ..                                   .. ..

    }                                          }         

2)函数的调用:直接使用“函数名”的形式调用;如果传递的值作为函数的位置参数,则可以使用“函数名 参数1 参数2 .. ..”的形式调用。

注意:函数的定义语句必须出现在调用之前,否则无法执行

示例:新建函数,用来创建一个目录,并切换到此目录

  # mycd(){                        //定义函数

    > mkdir $1

    > cd $1

    > }

    [root@svr5 ~]# mycd  /abc                            //调用函数

    [root@svr5 ~]# mycd  /360                            //调用函数

创建一个对2个整数求和的加法器:

function adder {

echo $[$1+$2]  }

示例:编写funexpr.sh脚本

    #!/bin/bash

    myexpr() {

        echo "$1 + $2 = $[$1+$2]"

        echo "$1 - $2 = $[$1-$2]"

        echo "$1 * $2 = $[$1*$2]"

        echo "$1 / $2 = $[$1/$2]"

    }

    myexpr $1 $2

    [root@svr5 ~]# chmod +x funexpr.sh

测试脚本执行效果 # ./funexpr.sh  43  21

 

示例:制作颜色函数脚本

#!/bin/bash

echo2(){

  echo -e "\033[$1m$2\033[0m"          //extend,扩展

}

# echo2 32 OK

 

示例:Shell版fork炸弹(仅13个字符):递归死循环,可迅速耗尽系统资源

#!/bin/bash

.()        //定义一个名为.的函数

{             //函数块开始

.|.&           //在后台递归调用函数.

 }            //函数块结束

;             //与下一条执行语句的分隔

.              //再次调用函数

#chmod +x test.sh

 

#./test.sh

#!/bin/bash

for i in {1..254}

do

ssh 192.168.4.$i “poweroff”

done

如果自己是4.10,则循环不能完全完成,这里可以使用continue命令

中断/退出及相关指令

类型

含义

break

跳出当前所在的循环体,执行循环体后的语句块,可以结束整个循环

continue

跳过循环体内余下的语句,重新判断条件以决定是否需要执行下一次循环,即结束本次循环

exit

退出脚本,默认的返回值是0

  #!/bin/bash

    for  i   in   {1..5}

    do

         [  $i  -eq  3 ] && break     

                         #这里将break替换为continue,exit分别测试脚本执行效果

               echo $i

    done

    echo 程序结束

示例:从键盘循环取整数(0结束)并求和,输出最终结果

    #!/bin/bash

    while  read  -p  "请输入待累加的整数(0表示结束):"     x

    do

        [ $x -eq 0 ]  &&  break

        SUM=$[SUM+x]

    done

    echo "总和是:$SUM"

示例:跳过1~20以内非6的倍数,输出其他数的平方值,设定退出代码为2

    #!/bin/bash

    i=0

    while  [ $i -le 20 ]

    do

        let i++

        [ $[i%6] -ne 0 ]  &&  continue

        echo $[i*i]

    done

    exit 2

 # chmod +x sum.sh

示例:利用位置参数获取2个整数,计算出它们的和;如果参数不够2个,则提示正确用法并退出脚本

#!/bin/bash

if [ $# -ne 2 ];then

echo “用法:$0 num1 num2”

exit 10                          //退出脚本,返回值设为10

fi

expr $1 + $2

 

 

shift

用来迁移位置变量,每执行一次,结果如下:丢弃$1,原$2变为$1

 

 

 

使用Shell完成各种Linux运维任务时,一旦涉及到判断、条件测试等相关操作时,往往需要对相关的命令输出进行过滤,提取出符合要求的字符串。

字符串处理

子串截取的三种方法

方法1:${var:起始位置:长度}          

echo  ${变量名:起始位置:长度}

变量=${变量名:起始位置:长度}

注:使用${}方式截取字符串时,起始位置是从0开始的(和数组下标编号类似),可省略。

# phone=15957488399     //定义变量

# echo ${phone}         //调用变量

# echo ${#phone}        //统计变量字符数

# echo ${phone:0:3}     //从变量的第0位截取到第3 位,可简写为echo ${phone::3}

方法2:expr  substr  "$字符串"  起始位置  长度   

expr  length  "$字符串" 

注:使用expr substr截取字符串时,起始位置的编号从1开始,这个要注意与${}相区分。

# expr substr "$phone" 1 3

# expr substr "$Phone" 9 11        //从左侧截取Phone变量的第9-11个字符

方法3 : echo "$字符串" |cut -b  起始位置-结束位置 //起始位置的编号从数字1开始

注:选项 -b 表示按字节截取字符,其中起始位置、结束位置都可以省略。当省略起始位置时,视为从第1个字符开始(编号也是从1开始,与expr类似),当省略结束位置时,视为截取到最后。

# echo $Phone | cut -b 1-6           //从左侧截取前6个字符

# echo $phone | cut -b 8-           //从第8个字符截取到末尾

# echo $phone | cut -b 9            //只截取单个字符,比如第9个字符

# echo $phone | cut -b 1,3,5        //分断截取单个字符,比如第1,3,5个字符

命令  | cut   -d  "分隔符:"  -f  列的编号

head  -5  /etc/passwd  |   cut  -d  ":"  -f  1,3

示例:随机生成8位密码

#!/bin/bash

key='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

pass=''

for i in `seq 8`

do

        num=$[RANDOM%62]

        tmp=`echo ${key:$num:1}`        //第1种方法

        pass=${pass}${tmp}

done

echo $pass

第2种方法:tmp=`expr substr "$key" $num 1`

第3种方法:tmp=`echo $key | cut -b $num`

 

路径分割

取目录位置:dirname “字符串”

取文档的基本名称:basename “字符串”

++++++++++++++++++++++++

获取文件名 basename   目录/文件名

获取目录名 dirname   目录/文件名

 

字符串替换的两种用法

只替换第1个匹配结果:${变量名/源数据/新数据}          ${var/old/new}

替换全部匹配结果:    ${变量名//源数据/新数据}         ${var//old/new}

echo  ${变量名/源数据/新数据}

变量名=${变量名/源数据/新数据}

 

按条件掐头去尾

字符串掐头从左向右匹配删除,返回值是删除后剩下的数据。

最短匹配删除  ${变量名#*关键词}

最长匹配删除  ${变量名##*关键词}

  注:用来删除头部,*通配

关键词的表示方式?  普通字符  通配符号

# A=`head -1 /etc/passwd`

# echo ${A#root}

# echo ${A#*:}

# echo ${A##*:}

字符串去尾从右向左匹配删除,返回值是删除后剩下的数据。

最短匹配删除 ${变量名%关键词*}

最长匹配删除 ${变量名%%关键词*}

注:用来删除尾部,*通配

# A=`head -1 /etc/passwd`

# echo ${A%:*}

# echo ${A%%:*}

示例:批量修改当前目录下的文件扩展名,将扩展名.doc改为.txt

for FILE in `ls *.doc`

do

mv $FILE ${FILE%.doc}.txt

done 

要适应不同扩展名文件的修改,并能够反向还原。修改前的扩展名、修改后的扩展名通过位置变量 $1、$2提供。改进的脚本编写参考如下:

for FILE in "$1"

do

    mv $FILE ${FILE%$1}"$2"

done

变量的初始值处理

初值的检测及设置

方法1:若变量已存在且非空则返回变量的值,反之返回自定义的值,原变量var的值不受影响。

${变量名:-"字符串"}            ${var:-word}

echo  ${变量名:-"字符串"}     

变量=${变量名:-"字符串"}

方法2:若变量存在且非空则返回变量的值,反之返回自定义的值。并赋值给没值的变量。

${变量名:="字符串"}            ${var:=word}

echo  ${变量名:="字符串"}

变量=${变量名:="字符串"}

#echo ${TT:-abc}   查看TT变量是否有值,有则显示TT的值,否则则显示abc

#echo ${TT:=abc}   查看TT变量是否有值,有则显示TT的值,否则则显示abc,并且给TT赋值abc

 

#!/bin/bash

read -p "确定要删除吗y/n?" sure

sure=${sure:-n}

if [ $sure == "y" ];then

rm -rf $1

fi

示例:创建用户,不输入密码,依然成功设置默认密码

read -p “请输入用户名:” name

read -p “请输入密码:” pass

[ -z $name ] && exit 

pass=${pass:-123456}                //如果用户没有输入密码,则默认密码为123456

useradd $name

echo “$pass” |passwd --stdin $name

示例:提示输入一个正整数x,求从1~x的和;若用户未输入值,则赋初值x=1,避免执行出错

方法一:

read -p “请输入一个正整数:” x

x=${x:-1}; i=1; SUM=0

while [ $i -le $x ]

do

let  SUM+=i; let i++

done

echo “从1到$x的总和是:$SUM”

方法二:

read -p "请输入一个正整数:" x

x=${x:-100}

sum=0

for i in `seq $x`

do

        let sum+=i

done

echo "从1到$x的总和是:$sum"

 

Shell数组

Shell对变量类型的管理比较松散,变量的值默认均视为文本,用在数学运算中时,自动将其转换为整数。

# var1=123          # var2=$var1+20 

# echo $var2       //123作为文本字串

# expr $var1 + 20    //123作为整数值

建立数组的方法:

方法一:若要定义数组的成员,可以在declare声明时定义,也可以直接整体赋值

格式:数组名=(值1 值2 ....值n)

示例:# MY_SVRS=(www ftp mail club)

      # set | grep "MY_"              //查看数组定义结果

方法二:并非每个成员都需要指定,下标也可以不连续,为单个元素赋值

格式:数组[下标]=值      //下标从0开始

示例:# WEB_SVRS[0]="www.tarena.com"          //为第1个元素赋值

      # WEB_SVRS[1]="mail.tarena.com"         //为第2个元素赋值     

      # WEB_SVRS[2]="club.tarena.com"          //为第3个元素赋值

查看数组元素的方法:

获取单个数组元素:${数组名[下标]}

获取所有数组元素:${数组名[@]}   或${数组名[*]}

获取数组元素个数:${#数组名[@]}  或${#数组名[*]}

获取连续的多个数组元素:${数组名[@]:起始下标:元素个数}

获取某个数组元素长度:${#数组名[下标]}

截取数组元素的一部分:${数组名[下标]:起始下标:字符数}

示例:# echo ${#SVRS[1]}          # echo $SVRS{[1]:1:2}

#a=(1 2 3 4) 定义数组

#a[0]=1 定义数组

#a[1]=ab

#a[2]=t12

#echo ${a[0]} 返回数组的一个值

#echo ${a[1]}

#echo ${a[2]}

#echo ${a[*]} 返回数组所有的值

#echo ${#a[*]}  返回数组有多少个值

示例:使用read命令从键盘读入用户指定的IP地址,每次读入一个,因为需要读多次,直到输入“EOF”时结束,所以可采用while循环结构,循环条件为输入的字符串不为“EOF”。要求用数组保存每次输入的IP地址,那肯定从下标为0的元素开始存放,赋值操作放在循环体内,下标的递增通过一个变量i控制。遇“EOF”结束while循环后,输出整个数组的内容,并显示数组元素的个数、第1个录入的IP地址。

#!/bin/bash

i=0                                                  //控制下标增长的变量

while :

do

    read -p "请添加IP地址(输EOF结束):"   IP

    [ $IP == "EOF" ] && break

    IPADDS[$i]="$IP"                          //每次录入赋值给不同的数组元素

    let i++

done

echo "您已录入的IP地址如下:"

echo ${IPADDS[@]}                                  //输出整个数组

echo "总共包括 ${#IPADDS[@]} 个地址,"                  //报告数组元素的个数

echo "其中第1个IP地址是:${IPADDS[0]}"              //输出第1个元素

# chmod +x getips.sh

 

 expect 预期交互

expect基于TCL编写的自动交互式程序。可以用在Shell脚本中,为交互式过程自动输送预先准备的文本或指令(比如FTP、SSH等登录过程),而无需人工干预;触发的依据是预期会出现的特征提示文本。

常见的expect指令

Ø 定义环境变量:set 变量名 变量值

Ø 创建交互式进程:spawn 交互式命令行

Ø 触发预期交互:expect "预期会出现的文本关键词:" { send "发送的文本\r" }

Ø 在spawn建立的进程中允许交互指令:interact

发邮件的几种方式:

1.不适合长邮件:echo “nihao” | mail -s 标题 收件人

2.脚本有依赖关系:mail -s 标题 收件人 < mail.txt

3.mail -s 标题 收件人 <

邮件内容

XXX

XXX

EOF

格式:expect <

  spawn 命令

  expect 输出 {send 输入}

  expect 输出 {send 输入}

      EOF

示例:实现SSH自动登录,并远程执行指令

# yum -y install expect

#!/bin/bash

expect <

spawn ssh 192.168.4.10

expect "password" {send "123456\n"}      #//等待屏幕出现password后自动发送密码

expect "#" {send "touch /a.txt\n"}    #//等待屏幕出现#号后自动输入命令,\n为回车

expect "#" {send "exit\n"}      #// 本行只要有命令即可,此行不执行,但不能缺省   

EOF

远程问题:

1.不知道是否有登陆提示

在SSH登录过程中,如果是第一次连接到该目标主机,则首先会被要求接受密钥,然后才提示输入密码

当然,如果SSH登录并不是第一次,则接受密钥的环节就没有了,而是直接进入验证密码的过程

rm -rf /root/.ssh/known_host

2.Ssh比较慢,

解决方法:把你的ssh调快一些/etc/ssh/sshd_config    #useDNS yes改为no; GSSAPI……yes改为no

将等待时间调长:set timeout 30

3.脚本的最后一行代码,不执行

#!/bin/bash

for i in 10 254

do

        set timeout 30                               #//定义变量

        rm -rf /root/.ssh/known_hosts

        expect <

        spawn ssh 192.168.4.$i                       #//创建交互式进程

        expect "yes/no"   {send "yes\n"}

        expect "password" {send "123456\n"}          #//自动发送密码

        expect "#" {send "touch /nihao.txt\n"}       #//允许交互式命令

        expect "#" {send "exit\n"}

EOF         //必须顶格写

done

#!/usr/bin/expect

spawn ssh localhost                              #//创建交互式进程

expect {

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

"password:" {send "Tarena0011\r"}                #//自动发送密码

}   

interact                                           #//允许交互式环境

 

禁止别人远程本机:# systemctl stop sshd

  # systemctl disable sshd

 

你可能感兴趣的:(Linux命令,脚本)