来自 阿铭linux视频号
练习1 备份 for循环
写一个shell脚本,遍历/data目录下的txt文件,将这些txt文件做一个备份,备份到当前目录,
备份的文件名增加一个年月日的后缀,比如将1.txt备份为1.txt_20240808
cat >1.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#定义后缀变量
suffix=`date +%Y%m%d`
#找到/data/目录下的txt文件,用for循环遍历
for i in `find /data/ -type f -name "*.txt"`
do
echo "备份文件$i"
cp ${i} ${i}_${suffix}
done
EOF
练习2 创建用户 if条件判断 for循环
写一个shell脚本,创建10个用户,并给他们设置随机密码,密码记录到一个文件里,文件名为userinfo.txt
用户从user_00 到 user_09
密码要求:包含大小写字母以及数字,密码长度15位。
cat >2.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#先查看/tmp/userinfo.txt文件是否存在,存在的话先删除,以免影响到本次脚本执行结果
if [-f /tmp/userinfo.txt]
then
rm -f /tmp/userinfo.txt
fi
#判断mkpasswd命令在不在,使用该命令生成随机字符串,也就是用户的密码
if ! which mkpasswd
then
yum -y install expect
fi
#借助seq生成从00到09,10个数的队列
for i in `seq -w 0 09`
do
#每生成一个随机字符串,将该字符串赋值给password变量
#mkpasswd命令默认生成的字符串会包含大小写字母、数字和特殊符号
#如果不要求特殊符号,可以检查-s 0来限定不适用特殊符号
password=`mkpasswd -l 15 -s 0`
#添加用户,并给该用户设置密码
useradd user_${i} && echo "${password}" | passwd --stdin user_${i}
echo "user_${i} ${password}" >>/tmp/userinfo.txt
done
EOF
#删除刚创建用户
cat >22.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
for i in `seq -w 0 09`
do
rm -f /tmp/userinfo.txt && userdel -r user_${i}
done
EOF
练习3 检查磁盘读写 for循环
写一个shell脚本,用来检测本机所有磁盘分区读写是否都正常
提示:可以遍历所有挂载点,然后新建一个测试文件,然后再删除测试文件,如果可以正常新建和删除,那说明该分区没有问题。
cat >3.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
for i in `df | sed '1d' | grep -v 'tmpfs' | awk '{print $NF}'`
do
#创建测试文件并删除,从而确定该磁盘分区是否有问题
touch $i/testfile && rm -f $i/testfile
if [ $? -ne 0 ]
then
echo "$i 读写有问题"
else
echo "$i 读写正常"
fi
done
EOF
练习4 检查文件权限 for循环if条件判断
写一个shell脚本,检查/data/wwwroot/app/目录下所有文件和目录的权限,看是否满足下面的条件:
所有文件的权限为644
所有目录的权限为755
文件和目录的所有者为www,所属组为root
如果不满足,改成符合要求的条件
cat >4.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
cd /data/wwwroot/app
#遍历所有文件和目录,用"find ."即可
for i in `find .`
do
#查看文件权限
file_p=`stat -c %a $i`
#查看文件所有者
file_u=`stat -c %U $i`
#查看文件所属组
file_g=`stat -c %G $i`
#判断是否为目录
if [ -d $i ]
then
[ $file_p != '755' ] && chmod 755 $i
else
[ $file_p != '644' ] && chmod 644 $i
fi
#&&用在命令中间可以,可以起到if判断的作用,&&连接的两个语句,当第一个执行成功之后,才会执行后面的命令
[ $file_u != 'www' ] && chown www $i
[ $file_g != 'root' ] && chown :root $i
done
#注释多行
<<'COMMENT'
另外可用find来实现
find /data/wwwroot/app/ -type d ! perm 755 -exec chmod 755 {} \;
find /data/wwwroot/app/ ! -type d ! perm 644 -exec chmod 644 {} \;
find /data/wwwroot/app/ ! -user www -exec chown www {} \;
find /data/wwwroot/app/ ! -group root -exec chgrp root {} \;
两个脚本相比,第一个只需要find一次,二第二个需要find四次,在文件很多的情况下,执行效率差
COMMENT
EOF
练习5 磁盘备份 函数 for循环 if条件判断
写一个shell脚本,需求如下
有一个目录/data/att/,该目录下有数百个子目录,比如/data/att/linux /data/att/mysql
然后再深入一层为以日期命名的目录,例如,/data/att/linux/20240808
每天会生成一个日期新目录,如/data/att/linux/20240809
由于/data/所在磁盘快满了,所以要将一年以前的文件移动到另外一个目录/data1/att下
示例: mv /data/att/linux/20240808 /data1/att/linux/20240808
移动完之后,还需要做软连接
示例:ln -s /data1/att/linux/20240808 /data/att/linux/20240808
写一个shell脚本,要求/data/att/下所有子目录都要按此操作,
脚本会每天01:00执行一次
提醒:要确保老文件成功挪到/data1/att下之后才能做软连接,需要有日志
cat >5.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#先定义一个main函数,目的是为了后面调用函数,方便记录日志
main()
{
cd /data/att
#遍历第一层目录
for dir1 in `ls`
do
#遍历第二层目录,用find只找当前目录下 一年以前的子目录
for dir2 in `find $dir1 -maxdepth 1 -type d -mtime +365`
do
#将目标目录下的文件同步到/data1/att目录下,注意这里-R可以自动创建目录结构
rsync -aR $dir2/ /data1/att/
if [ $? -eq 0 ]
then
#如果同步成功,会将/data/att下的目录删除
rm -rf $dir2
echo "/data/att/$dir2 移动成功"
#创建软链接
ln -s /data1/att/$dir2 /data/att/$dir2 && \
echo "/data/att/$dir2 成功创建软链接"
#输出空行
echo
else
echo "/data/att/$dir2 未移动成功"
fi
done
done
}
#调用main函数,并将输出写入日志里,日志每天一个
main &> /tmp/move_old_data_`date +%F`.log
<<'COMMENT'
通过main函数的形式,方便定义脚本日志
find 使用-maxdepath 定义查找目录层级
rsync -R 选项可以自动级联创建目录层级
COMMENT
EOF
#定时任务
crontab -e
0 1 * * * /root/5.sh &>/dev/null 2>&1
练习6 系统负载监控 while死循环 if条件判断
写一个shell脚本,监控系统负载,如果系统超过10,需要记录系统状态信息。提示:
系统负载命令使用uptime看,过去1分钟的平均负载
系统状态使用如下工具标记:top/vmstat/ss
要求每隔20s监控一次
系统状态信息需要保存到/opt/logs下面,保留一个月,文件名建议带有时间戳`date +%s`后缀或前缀
cat >6.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#判断/opt/logs目录是否存在,不存在就创建 ||前后链接的命令,前面执行不成功,执行后面
[ -d /opt/logs ] || mkdir -p /opt/logs
#while死循环
while :
do
#获取系统1分钟的负载,并且只取小数点前面的数字
load=`uptime | awk -F 'average:' '{print $2}' | cut -d ',' -f1 | sed 's/ //g' | cut -d '.' -f1`
if [ $load -gt 10 ]
then
#分别记录top/vmstat/ss命令执行的结果
top -bn1 | head -n 100 >/opt/logs/top.`date +%s`
vmstat 1 10 >/opt/logs/vmstat.`date +%s`
ss -an >/opt/logs/ss.`date +%s`
fi
#休眠20秒
sleep 20
#查看30天以前的日志文件删除
find /opt/logs -type f \( -name "top*" -o -name "vmstat*" -o -name "ss*" \) -mtime +30 | xargs rm -f
done
<<'COMMONT'
||与&&相反,||连接的两个命令,当前面的命令不成功,就会执行后面的命令
死循环可以使用while : + sleep 组和
()代表一个整体,find里面可以使用()将多个条件组和起来,当成一个整体处理
COMMONT
EOF
练习7 记录新生成的文件 if条件判断 定时任务
写一个shell脚本,有一台服务器作为web服务器,有一个目录(/data/web/attachment)
不定时会被用户上传新的文件,因为不知道什么时候会上传
所以需要5分钟做一次检测,是否有新文件生成
检测完成后若是有新文件,还需要将新文件的列表输出到一个按照年月日时分为名的日志文件中
思路:每5分钟检测一次,需要有一个计划任务,每5分钟去执行一次
脚本检测的时候,就是使用find命令查找5分钟有过更新的文件
若是有更新,那这个命令会输出东西,否者是没有输出
所以,可以把输出结果的行数作为比较对象,看看是否大于0
cat >7.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#日志文件名,包含年月日时分
datetime=`date +%Y%m%d%H%M`
basedir=/data/web/attachment
#find找到5分钟之内新产生的文件,并把文件列表写入一个文件里
find $basedir/ -type f -mmin -5 >/tmp/newf.txt
#如果文件里面有内容,把文件改名字,即我们要的文件列表日志文件
if [ -s /tmp/newf.txt ];then
/bin/mv /tmp/newf.txt /tmp/$datetime
fi
<<'COMMENT'
find 的-mmin 选项以分钟为时间单位查找
[ -s filename ] 表示当文件存在,并且文件内容不为空时,条件成立
COMMENT
EOF
#定时任务
crontab -e
*/5 * * * * /root/7.sh &>/dev/null 2>&1
练习8 read用户交互传参 while死循环 case选择
写一个shell脚本,实现如下功能
输入一个数字,然后运行对应的一个命令
显示命令如下:
*cmd meau** 1-date 2-ls 3-who 4-pwd
当输入1是会运行date,输入2时会运行ls,以此类推
cat >8.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#先把提示语打印出来
echo "*cmd meau** 1-date 2-ls 3-who 4-pwd"
#使用死循环,目的是为了当用户输入的字符并非要求的字符时,不能直接退出脚本,而是再次重新开始
while :
do
#然后使用read实现和用户的交互,提示让用户输入一个数字
read -p "please input a number 1-4: " n
case $n in
1)
date
##之所以要用break,因为当用户执行完命令就要退出脚本了
break
;;
2)
ls
break
;;
3)
who
break
;;
4)
pwd
break
;;
*)
#如果输入的并不是1-4的数字,提示出错
echo "Worng input,try again!"
;;
esac
done
<<'COMMENT'
read -p可以在shell脚本中实现和用户交互的效果
case ... esac 这种逻辑判断用法,非常适合做选择题,尤其是选项很多时,选项也可以有多个值,比如1|5)
如果想要反复和用户交互,必须使用while循环,并借助break或continue控制循环流程
break表示退出循环体,continue表示结束本次循环
COMMENT
EOF
练习9 read用户交互传参 while死循环 if条件判断
写一个shell脚本,执行后,打印一行提示"Please input a number:"
要求用户数值,然后打印出改数值,然后再>次要求用户输入数值。
直到用户输入"end"停止
cat >9.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#死循环,做到让用户反复输入
while :
do
read -p "Please input a number: (Input "end" to quit ) " n
#使用sed将用户输入的字符串中数字替换为空,如果是纯数字,那么num的值为1
#为什么是1而不是0呢?因为wc -c会把字符串结尾也标记为1个字符
num=`echo $n | sed -r 's/[0-9]//g' | wc -c`
if [ $n == "end" ]
then
exit
fi
if [ $num -ne 1 ]
then
echo "What you input is not a number! Try again!"
else
echo "The number you entered is: $n"
fi
done
<<'COMMENT'
wc -c 计算字符串的长度,其中回车也算是一个字符
使用sed 's/[0-9]//g' 可以将字符串里的数字删除
exit 直接退出脚本
COMMENT
EOF
练习10 监控网站 if条件判断
写一个shell脚本,监控某站点访问是否正常
提示:
可以将访问的站点以参数的形式提供,例如 sh xxx.sh www.alibaby007.com
状态码为2xx或3xx表示正常
正常时echo正常,不正常时echo不正常
cat >10.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#检查本机有没有curl命令
if ! which curl &>/dev/null
then
echo "本机没有安装curl"
#这里假设系统为centos
yum -y install curl
if
[ $? -ne 0 ]
then
echo "curl未安装成功"
exit 1
fi
fi
#获取状态码
code=`curl --connect-timeout 3 -I $1 2>/dev/null | grep 'HTTP' | awk '{print $2}'`
#如果状态码是2xx或3xx,则条件成立
if echo $code | grep -qE '^2[0-9][0-9]|^3[0-9][0-9]'
then
echo "$1 访问正常"
else
echo "$1 访问不正常"
fi
<<'COMMENT'
curl -I 只输出header
if grep - 200 用于判断200是否存在 -q静默输出
COMMENT
EOF
练习11 传参 加减乘除取大取小 函数
写一个shell脚本,使用传参的方式实现加减乘除的功能
例如 sh a.sh 1 2 然后分别计算加减乘除的结果
要求:
脚本需要判断提供的两个数字必须为整数
当做减法或除法时,需要判断哪个数字大
减法时需要用大的数字减小的数字
除法时需要用大的数字除以小的数字,并且结果需要保留两个小数点
cat >11.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#先判断参数个数是不是2个
if [ $# -ne 2 ]
then
echo "The number of parameter is not 2, Please userage:./$0 1 2"
exit 1
fi
#判断提供的参数是不是正整数
is_int()
{
if echo "$1" | grep -q '[^0-9]'
then
echo "$1 is not integer number."
exit 1
fi
}
#找大数
max()
{
if [ $1 -ge $2 ]
then
echo $1
else
echo $2
fi
}
#找小数
m()
{
if [ $1 -lt $2 ]
then
echo $1
else
echo $2
fi
}
#加法
sum()
{
echo "$1 + $2 = $[ $1 + $2 ]"
}
#减法
minus()
{
big=`max $1 $2`
small=`min $1 $2`
echo "$big - $small = $[ $big - $small ]"
}
#乘法
mult()
{
echo "$1 * $2 = $[ $1 * $2 ]"
}
#除法
div()
{
big=`max $1 $2`
small=`min $1 $2`
d=`echo "scale = 2 ; $big / $small " | bc`
echo "$big / $small = $d"
}
#调用各个函数
is_int $1
is_int $2
sum $1 $2
minus $1 $2
mult $1 $2
div $1 $2
<<'COMMENT'
$# 表示参数的个数
$0 表示脚本名字
脚本的参数为$1 $2 $3 ...
scale = 2 表示保留小数点后两位
脚本中函数的用法,函数也支持参数
COMMENT
EOF
练习12 传参 while循环 continue和break用法
写一个shell脚本,带参数,实现下载文件的效果,参数有两个
第一个参数为文件下载链接
第二个参数为目录,即下载后保存的位置
注意:要考虑目录不存在的情况,脚本需要提示用户是否创建目录
cat >12.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#判断是否存在wget命令
if ! which wget &>/dev/null
then
echo "系统不存在wget命令"
#安装wget命令
yum -y install wget
if [ $? -ne 0 ]
then
echo "wget安装失败"
exit 1
fi
fi
#先判断参数个数是不是2个
if [ $# -ne 2 ]
then
echo "The number of parameter is not 2, Please userage:./$0 url dir"
exit 1
fi
#无限循环,目的是为了创建目录
while :
do
#目录存在,跳出循环
if [ -d $2 ]
then
break
else
#目录不存在,会询问是否创建
read -p "目录不存在。是否要创建?" yn
case $yn in
y|Y)
mkdir -p $2
break
;;
n|N)
#当用户输入n,意味着他不想创建目录,然后脚本直接退出即可
exit 2
;;
*)
#如果用户输入的提示词不符合要求,则需要再次询问用户
echo "你只能输入y或n"
continue
;;
esac
fi
done
#进入到目标目录
cd $2
#使用wget命令来下载,这里假设wget命令存在,并且用户提供的链接也是没问题的
wget $1
if
[ $? = 0 ];then
echo "下载成功"
exit 0
else
echo "下载失败,请检查url是否可以访问,目标目录是否已存在要下载文件"
exit 1
fi
<<'COMMENT'
exit 0 执行成功并退出
exit 1 出错退出
exit 2 严重出错退出
break 直接退出脚本 退出循环体 continue 继续执行脚本,进入下一次循环
read -p 使用在和用户交互的场景下
下载的文件是否存在未判断,可以增加md5值判断
COMMENT
EOF
练习13 随机数函数 传参 while 循环用法,continue和break用法
写一个shell脚本,多人抽签游戏,每人执行脚本产生一个随机数,要求如下
脚本执行后,输入人名,产生1-99之间的数字
相同的名字重复执行,抓到的数字应该和之前的保持一致
前面出现过的数字,下次不能再出现
需要将名字和对应的数字记录到一个文件里
脚本一旦运行,除非按ctrl+c停止,否则要一直运行
cat >13.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#假设记录名字和数字的文件为/tmp/name.log
#文件格式为: alibaby:number 例如 alibaby:99
#创建生成随机数的函数
create_number()
{
#当遇到已经出现过的数字,需要自动再次生成随机数,用while循环实现
while :
do
#$RANDOM为一个随机数字,范围0-32767
#为了获取1-99之间的随机数,需要将$RANDOM除以99取余数,但余数范围为0-98,所以再加1就能得到我们想要的数字
num=$[ $RANDOM%99+1 ]
#如果数字出现在了/tmp/name.log里,则n>0,n就是数字出现的次数
n=`awk -F ':' -v NUMBER=$num '$2 == NUMBER' /tmp/name.log | wc -l `
if [ $n -gt 0 ]
then
continue
else
echo $num
break
fi
done
}
#while循环实现脚本不退出
while :
do
#和用户交互,输入名字
read -p "Please input a name: " name
if [ ! -f /tmp/name.log ]
then
#当记录名字和数字的文件不存在时,也就是说改脚本第一次执行时
#什么都不用考虑,直接打印数字即可
number=$[ $RANDOM%99+1 ]
echo "Your number is: $number"
echo "$name:$number" >/tmp/name.log
else
#如果输入的名字出现在/tmp/name.log,则n>0,n就是名字出现的次数
n=`awk -F ':' -v NAME=$name '$1 == NAME' /tmp/name.log | wc -l`
if [ $n -gt 0 ]
then
echo "The name already exist."
awk -F ':' -v NAME=$name '$1 == NAME' /tmp/name.log
continue
else
number=`create_number`
fi
echo "Your number is: $number"
echo "$name:$number" >>/tmp/name.log
fi
done
<<'COMMENT'
awk调用shell中的变量用法,awk -v NAME=$name '$1 == NAME' awk中$有自己含义,需要转换-v,NAME为awk的变量
while 循环用法,continue和break用法
$RANDOM用法
函数以及调用函数并赋值变量的用法
COMMENT
EOF
练习14 日志归档脚本 for + seq 用法
写一个日志归档脚本,类似于系统的logrotate程序做日志归档
假如服务的输出日志是1.log,要求每天归档一个,1.log第2天就变成1.log.1
第3天1.log.2,第4天1.log.3,一直到1.log.5
思路:
要考虑到该脚本可能是初次执行,也可能是已经执行了好久
如果是初次执行,那么日志目录里只有1.log,而没有1.l0g.1,1.l0g.2,... 1.l0g.5
又或者说这些文件有部分或者全部,这些情况我们都要考虑到
就说最常规的一个情况:这些文件都存在
那么,我们就需要先删除掉最后面的那个1.l0g.5
然后1.log.4改名字为1.log.5
再然后1.log.3改名字为1.og.4,以此类推
cat >14.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#假设日志路径为/data
cd /data
#首先删除掉最老的日志1.log.5,如果存在的话
if [ -f 1.log.5 ]
then
rm -f 1.log.5
fi
#使用for + seq 做从5到2倒序遍历循环
#这里的用法,等同于 for i in 5 4 3 2
for i in `seq 5 -1 2`
do
#如果日志存在,则后缀加1
if [ -f 1.log.$[$i-1] ]
then
mv 1.log.$[$i-1] 1.log.$i
fi
done
#还差最后一个也要改名字
mv 1.log 1.log.1
#还要新建一个1.log
touch 1.log
<<'COMMENT'
for + seq 用法
倒序是脚本的关键,因为要从最后面的文件开始处理,就好比一个萝卜一个坑
只有最前面的腾出地方,后面的才能到之前的坑里
COMMENT
EOF
练习15 传参 $#表示参数的个数,${#1} 第一个参数的长度
写一个shell脚本,判断给定的一串数字是否是合法的日期,比如20231301就不合法
cat >15.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#判断是否是一个参数,并且判断长度是否是8位
if [ $# -ne 1 ] || [ ${#1} -ne 8 ]
then
echo "Uasge: bash $0 yyyymmdd"
exit 1
fi
#变量转换$1转换为mydate
#mydate:0:4
mydate=$1
#截取前4个字符,从第0个取(从0开始的),取4个
year=${mydate:0:4}
#截取第5到第6个字符,mydate:4:2从第5个取,取2个
month=${mydate:4:2}
#截取第7到第8个字符,mydate:6:2从第7个取,取2个
day=${mydate:6:2}
#用cal判断日月年是否是合法,正确错误的结果输出到空
if cal $day $month $year >/dev/null 2>/dev/null
then
echo "The date is OK. The date is $year年$month月$day日"
else
echo "The data is Eroor."
fi
<<'COMMENT'
$#表示参数的个数,${#1} 第一个参数的长度
字符串的分片,${a:n:m} 表示变量a从第n(n从开始计算)个字符开始截取,一共截取m个
cal 打印日历的命令,cal 日 月 年 cal 09 08 2024 ;cal 月 年 cal 08 2024;cal 年 cal 2024
COMMENT
EOF
练习16 数组 a[0]=1;a[1]=2 [ -n "$m" ] [ -z "$m" ]
写一个shell脚本,随机3为随机数,并且可以根据用户的输入参数来判断输出几组
比如,脚本名字为abc.sh
执行方法
bash abc.sh 产生1 组3位数字
bash abc.sh 10 产生10组3位数字
#思路随机产生一个1位数字,然后产生3次,再然后将3个数字组合在一起
cat >16.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#产生一位数字的函数
get_a_num()
{
#除以10取余
n=$[ $RANDOM%10 ]
echo $n
}
#组合三位数字的函数
get_numbers()
{
for i in 0 1 2;do
#数组赋值
a[$i]=`get_a_num`
done
#将多余的空格删除掉
echo ${a[@]} | sed 's/ //g'
}
#参数个数判断
if [ $# -gt 1 ]
then
echo "The number of your parameter can only be 1."
echo "example: bash $0 5"
exit
fi
#如果没有提供参数,那么直接产生一个3位数字
#如果提供了参数,要判断参数是否是一个正整数
if [ $# -eq 1 ];then
#将所有数字删除,删除完后如果是空,就说明是纯数字
m=`echo $1 | sed 's/[0-9]//g'`
if [ -n "$m" ];then
echo "Uasge bash $0 n,n is a number ,example: bash $0 5"
exit 1
else
echo "The number are:"
for i in `seq $1`
do
get_numbers
done
fi
else
get_numbers
fi
<<'COMMENT'
数组可以以元素为单位赋值,a[0]=1;a[1]=2,获取数组的值,echo ${a[@]},但是元素之间有空格
判断一个字符是否是存数字,可以n=(echo $a | sed 's/[0-9]//g'),n为空说明是纯数字
[ -n "$m" ] 判断一个变量的值$m不为空
[ -z "$m" ] 判断一个变量的值$m为空
COMMENT
EOF
练习17 遍历文本的行 while read line
写一个shell脚本,有两个文件a.txt和b.txt,需求
把a.txt中有但b.txt中没有的行找出来,并写到c.txt
然后计算c.txt的行数
grep -vwf a.txt b.txt 可以实现,但-w是单词的意思,有字符中间有空格不行。
cat >17.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#如果存在c.txt先删除
[ -f c.txt ] && rm -f c.txt
#使用while循环遍历a.txt所有行,line可自定义
cat a.txt | while read line
do
#如果b.txt里面没有这行内容,将其写入c.txt
if ! grep -q "^${line}$" b.txt
then
echo ${line} >>c.txt
fi
done
#计算c.txt行数
wc -l c.txt
<<'COMMENT'
遍历文本的行 while read line ,如果用for,那么一行中有空格会被拆分
COMMENT
EOF
练习18 函数与case
写一个shell脚本,可以接受选项[i,I],完成下面任务:
使用以下形式:xxx.sh [-i interface | -I ip]
当使用-i选项时,显示指定网卡的IP地址
当使用-I选项时,显示指定IP所属的网卡
例如:sh xxx.sh -i ens33
sh xxx.sh -I 192.168.0.1
当使用除[-i | -I]选项时,显示[-i interface | -I ip]次信息
当用户指定信息不符合时,显示错误。(比如指定的eth0没有,而实际是eth1时)
cat >18.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#创建打印脚本使用帮助的函数
useage()
{
echo "Please useage: $0 -i 网卡名字 or $0 -I ip地址"
}
#当参数不等于2,要提示脚本的使用帮助信息
if [ $# -ne 2 ]
then
useage
exit
fi
#将本机所有网卡名字全部获取,暂记如临时文件
ip add | awk -F ":" '$1 ~ /^[1-9]/ {print $2}' | sed 's/ //g' >/tmp/eths.txt
#或ifconfig | grep mtu | awk -F ":" '{print $1}' >/tmp/eths.txt
#接下来会将本机所有网卡以及对应IP记录到eth_ip.log文件里
#但在执行脚本时,会先看是否有该文件,有的话删除掉
[ -f /tmp/eth_ip.log ] && rm -f /tmp/eth_ip.log
#遍历网卡
for eth in `cat /tmp/eths.txt`
do
#获取网卡对应的IP地址
ip=`ip add | grep -A2 ": $eth" | grep inet | awk '{print $2}' | cut -d '/' -f 1`
echo "$eth:$ip" >>/tmp/eth_ip.log
done
#删除临时文件
del_tmp_file()
{
[ -f /tmp/eths.txt ] && rm -f /tmp/eths.txt
[ -f /tmp/eths_ip.txt ] && rm -f /tmp/eth_ip.txt
}
#当提供的网卡名字错误时要报错
wrong_eth()
{
if ! awk -F ':' '{print $1}' /tmp/eth_ip.log | grep -qw "^$1$"
then
echo "请指定正确的网卡名字"
del_tmp_file
exit
fi
}
#当提供的IP地址错误时要报错
worng_ip()
{
if ! awk -F ':' '{print $2}' /tmp/eth_ip.log | grep -qw "^$1$"
then
echo "请指定正确的ip地址"
del_tmp_file
exit
fi
}
#根据第一个参数来决定执行什么指令
case $1 in
-i)
worng_eth $2
grep -w $2 /tmp/eth_ip.log | awk -F ':' '{print $2}'
;;
I)
wrong_ip $2
grep -w $2 /tmp/eth_ip.log | awk -F ':' '{print $1}'
;;
*)
useage
del_tmp_file
exit
;;
esac
del_tmp_file
<<'COMMENT'
$1 ~ /^[1-9]/ $1第一段 ~匹配 /^[1-9]/ 以数字开头的行
边写边调试
临时文件可以大大降低写shell脚本的难度,但不要忘记在脚本执行结束时删除掉。
巧用函数,减少冗余代码
COMMENT
EOF
练习19 检查系统服务 函数
编写一个shell脚本,用来检测系统里面的所有服务是否都正常运行
假定,系统运行的服务有nginx/mysql/redis/tomcat
要求脚本有内容输出,可以明确告知服务是否正常运行
提示:
如果服务进程存在并且端口在监听,说名服务正常
nginx 443
mysql 3306
redis 6379
tomcat 8825
进程是否存在使用pgrep | grep 'xxx'
端口是否存在使用 ss -lnp | grep 'xxx'
cat >19.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
#判断pgrep或ss命令是否存在
check_tools()
{
if ! which pgrep &>/dev/null
then
echo "本机没有pgrep命令"
exit 1
fi
if ! which ss &>/dev/null
then
echo "本机没有ss命令"
exit 1
fi
}
#使用pgrep来检测某服务进程是否存在
#该函数只有返回值为0或者1
#当返回值为0,说明进程存在,返回值为1说明进程不存在
check_ps()
{
if pgrep "$1" &>/dev/null
then
return 0
else
return 1
fi
}
#使用ss -lnp 来检测端口是否是存在
check_port()
{
port_n=`ss -lnp | grep ":$1 " | wc -l`
if [ $port_n -ne 0 ]
then
return 0
else
return 1
fi
}
#只有check_ps和check_port同时返回值为0,才能说明指定服务是正常的
check_srv()
{
if check_ps $1 && check_port $2
then
echo "$1服务正常"
else
echo "$1服务不正常"
fi
}
check_tools
check_srv nginx 443
check_srv mysql 3306
check_srv redis 6379
#tomca服务要检查有没有java进程
check_srv java 8825
<<'COMMENT'
如果一条命令的结果作为if的判断条件,则当命令执行成功时条件为真,也就是说当返回值为0时,条件为真
pgrep后面跟进程名关键字,即可将相关进程的PID列出来
巧用函数,减少冗余代码
COMMENT
EOF
练习20 监控CPU使用率 while循环
写一个shell脚本,用来监控服务器CPU的使用率
思路:使用top -bn1命令,取当前空闲CPU百分比值(只取整数部分),然后用100去减这个数值。
cat >20.sh<<'EOF'
#!/bin/bash
#auth:alibaby007
#version:v1
#date:2024-08-08
while :
do
#先把CPU idle的值获取到
idle=`top -bn1 | sed -n '3p' | awk -F 'ni,' '{print $2}' | cut -d. -f1 | sed 's/ //g'`
use=$[100-$idle]
if [ $use -gt 90 ]
then
echo "CPU use percent too high,value is $use%"
#发邮件省略
fi
sleep 10
done
<<'COMMENT'
系统定时任务crond,最小粒度是1分钟,若可接受,任务计划还是比较稳定的
while slepp 有中断的风险
COMMENT
EOF