函数及中断控制
函数:在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