目录
Toggleshell习题:
对shell的理解
一些简单的shell脚本就是命令堆叠+三剑客+语法
语法:变量,判断,循环,函数,数组
shell命令解释器:bash —红帽系列
电脑写脚本工具:sublime(命令自动补全)
脚本分类:
检查类:系统巡检,系统加固
系统优化(初始化)
nginx优化,tomcat优化
通过案例,习题,练习写脚本的思路,分析阅读其他人写的脚本。
shell基操:之前学过的命令四剑客正则,服务的使用
目标:
- 能够写出超过300行的脚本
- 总计完成书读写5000行脚本
- 完成100个脚本书写
一方面工作要求,一方面开发算法
前面内容复习要求:https://www.yuque.com/lidao996/sre/vc088y5l9zof946c?singleDoc#
书写脚本的注意事项
- 是否要判断root用户运行(安装软件等)
- 参数个数
- 参数类型是否为数字
- 判断文件/目录是否存在
- 命令是否存在
- 日常思考是否要进行判断/检查
流程:
- 分析需求
- 书写步骤/命令—每一步的目的和用到的命令
- 书写脚本并进行测试
- 手动/自动—看是否与定时任务配合
涉及到脚本中由于系统语言不一致导致的命令结果不同可以在脚本开头临时改为英文语言和字符集export LANG=en_us.UTF-8
当脚本中有多个变量导致变量名冲突时可以用到局部变量local
shell脚本中的shift: 转移,把第二个参数变成第一个(参数向左移一位),系统脚本中用的比较多
shell脚本执行方式
#!指定脚本的命令解释器
bash—-#!/bin/bash
python—-#!/usr/bin/python或#!/usr/bin/env python
Ubuntu/Debian系统中自带dash,使用sh执行脚本会自动用dash
使用bash运行脚本会创建一个子shell进程执行,通过.或source执行脚本会在当前命令中直接执行(需要在当前环境下生效)
所以配置系统环境变量之后用source加载
变量
命名规则:不能以数字开头,取名尽量有意义,几个单词之间用下划线_连接,如01.check_user_exist.sh
分类:
普通变量(用户局部变量)~/.bashrc ~/.bash_profile
环境变量(全局变量),系统自带 env命令查看系统所有全局变量,一般名字为大写字母(PS1)–/etc/profile
环境变量文件读取(加载)顺序
特殊变量(常用):方便对参数,命令结果进行判断和对比
1.位置相关的特殊变量
$n n>9 会表示为$1+0 第一个参数+0,使用${11}表示即可
当以上的变量在if中做判断时候,会用到比较运算符
-eq =/== 等于 equal
-ne != 不等于
-lt < 小于 less than
-le <= 小于等于 less equal -gt > 大于 great than
-ge >= 大于等于 great equal
当if [ $? -eq 0 ]时,=会默认进行字符串比较,而eq会做整数的数值比较,-eq:效率更高:=:字符串比较,变量为空会报错,效率低
$@和$*相同点:取出脚本执行时输入的所有参数,大部分和循环一起用作为for i in “$@”
只有二者都在循环中且都用””时
“$@”:所有参数是独立的参数,有几个就循环几次
“$*”:所有参数是一个参数,只循环一次
shift 移动,参数向左移动一位
状态相关的特殊变量
${#变量}:统计变量的字符数== echo $a | wc -L
${变量/变/量}→把变改成量→量量== echo $a | sed ‘s#变#量#g’
故障案例:windows环境下用sublime软件写完脚本传到linux中,脚本文件执行异常,vim查看每次换行都有个^M符号
原因:windows下回车换行对应符号\r\n,linux回车换行符号\n,导致每一行结尾有问题
解决:用vim中的:%s#^M##g替换结束标记即可,或者用dos2unic命令+文件名 转换文件为linux格式
变量子串:用于对变量处理加工,(统计变量中字符串数量,对变量内容进行替换/删除….)
变量子串
格式${变量},写为$变量格式无法识别
应用:可以不用,但是这个效率高
截取,切片功能
等同于cut -f 1—取一行中的第一列(第一个字符)
cut -c 1-3—取1-3个字符
var=zhangpeng 将每个字符做标记
zhangpeng对应第012345678个字符
echo ${var:1} hangpeng —从第1个字符开始显示
echo ${var:1:2} ha —–从标记为1的开始取2个字符
echo ${var:1:3} han—–从标记为1的开始取3个字符
替换功能:
echo ${var/eng/111} zhangp111
等同于
echo $var | sed ‘s#eng#111#g’
统计变量中字符个数功能
echo ${#var} 9
等同于
echo $var | wc -L
变量扩展(设置默认值)–了解

变量赋值语句

read -p “提示” pass a
#表示把输入的信息都写入pass和a变量,中间用空格分隔
read -t 3 -p “提示” pass
#3秒内无操作自动退出
read -s -t 3 -p “提示” pass(不显示输入内容)
shell编程必知必会的计算
运算符
取随机数:随机的数字(n)对一个数字取余可以得到0到n-1范围的随机数。
取1-100以内的随机数
echo “$RANDOM%100 +1” | bc
等同于seq 100 | sort -R(随机排序) | head -1(取第1行)
运算方法(工具):推荐用awk;bc -l默认20位小数点精度且省略小数点前的0
awk计算
awk ‘BEGIN {print 1/3}’ 在读取文件之前运行,后面不接文件
1.直接运算awk -v total=4000 -v userd=2000 ‘BEGIN {print used/total}’
2.运算变量(常用)total_mem=4000,used_mem=2000
awk -v a=$total_mem b=used_mem ‘BEGIN {print b/a}’
bc计算:需要以echo “” | bc -l的形式
echo “2000/4000” | bc -l
echo “$used_mem / total_mem” | bc -l
用bc计算乘法时必须加上echo后面的””或者加上转义字符\:echo ” a * b”/echo a\* b | bc,否则*会被当初通配符报错
expr–作为是否是数字的检查
正常使用计算:格式为expr num1 + num2
使用*做乘法运算报错的原因:当前目录下有文件就会执行失败,需要添加转义字符\*才能正常运算
实现方法:查看 (expr + 变量) 的返回值
如果返回值为0或1,说明命令正常执行,变量是数字
如果与返回值为2或3…,说明命令执行失败,变量不是数字
在Shell脚本中,"$file"
和 ${file}
在大多数情况下可以互换,但存在关键区别:
1. 变量替换形式
$file
:基础变量展开${file}
:明确变量边界的标准形式(推荐)
推荐使用 ${}
的原因:
# 当变量后紧跟其他字符时,必须使用 ${}
filename="image"
echo ${filename}.jpg # 正确输出:image.jpg
echo $filename.jpg # 会被解析为 $filenamejpg(变量不存在)
2. 引号的作用
"$file"
:带引号的变量(防分词)${file}
:不带引号的变量(存在分词风险)
关键区别示例:
file="my document.txt"
# 不加引号的情况(危险!)
[ -f ${file} ] → 实际解析为 [ -f my document.txt ]
# 等价于同时检测两个文件:-f my 和 document.txt
# 加引号的情况(安全)
[ -f "$file" ] → 正确解析为 [ -f "my document.txt" ]
3. 最佳实践组合
同时使用 ${}
和双引号:
# 既明确变量边界,又防止分词
[ -f "${file}" ]
4. 特殊情况处理
当需要处理以下内容时,必须使用双引号:
- 含空格的文件名:
"My Report.pdf"
- 含通配符的文件名:
"file*.txt"
- 空变量保护:
[ -f "${file}" ]
(即使$file为空也不会报错)
各种判断
条件表达式(最基础)
属于判断中的核心,if后面都在用它,可以和&& ||一起用
目标:熟练掌握格式,根据判断目标选择用哪种格式(文件、大小、字符串、与或非),[]中写-a的格式少用,可能引起歧义,&比|的优先级更高
[ $1=n -a $2=n ] ==== [ $1=n ] && [ $2 = n ]
[[ $1=n && $2 = n ]] ==== [[ $1=n ]] && [[ $2 = n ]]
案例16:[] 与 [[]] 区别
常用条件表达式符号表
整数比大小符号表
判断文件是否存在
[ -f /etc/hosts ] &&echo “成立 ” || echo “失败”
test -f /etchosts && echo “成立 ” || echo “失败”
判断目录是否存在
[ -d /etc/ ] && echo “成立 ” || echo “失败”
成立
案例17:通过read或脚本传参判断是否为文件/目录/软连接/有无执行权限
1.两种传参方式选其一
2.判断参数是否为空
判断是否为软连接 是定义type=软连接 匹配了L之后就不会匹配后者文件/目录
判断是否有执行权限 [-x $1] 是定义perm=有权限 不是定义无权限
判断是否为文件,是文件定义type=文件, && perm=”有执行权限” || perm=”没有执行权限”
判断是否为目录,是目录定义type=目录 , && perm=”有执行权限” || perm=”没有执行权限”
以上都不匹配输出以上的类型都不是
输出 此文件类型$type ,权限 $perm
对比字符串:必须在=两侧的内容加””
-z 是否为空,为空则真
-n是否不为空,不为空则真
案例18:检查selinux是否关闭的脚本,如果没有关闭提示是否要关闭,输入yes关闭,其他不关闭
1.定义变量
配置文件取出状态字符串
sed -nr ‘/^SELINUX=/p’ /etc/selinux/config | awk -F= ‘{print $2}’
命令取出状态字符串 getenforce
2.判断是否为空
3.判断以上两个字符串是否同时等于disabled→→→selinux彻底关闭
4.read接收用户输入信息,yes则使用sed -i “s###g”修改
no则退出
两个条件同时满足用逻辑判断表示(与或非)
使用正则判断 变量里是否有数字
正则部分不加””
[[ $str =~ [0-9] ]] && echo 成功 || echo失败
有数字则为成功,没有失败
案例20:书写脚本通过read读取输入,判断输入是否为整数,浮点数,字符串
整数,浮点数都包含负数
1.read获取变量
2.判断变量是否为空,为空退出
3.正则匹配 ?表示前一个字符出现0次或1次 .默认匹配任意一个字符,匹配.需要加\转义
整数 ^[+-]?[0-9]+$
浮点数 ^[+-]?[0-9]+\.[0-9]+$ 匹配小数点前后都有值的小数
字符串(匹配规则还不完整) ^[a-zA-Z0-9]+$ 也会匹配整数→解决办法:先判断整数
if判断 三种格式
1) 单分支判断
引用场景:与 &&或 ||类似,用于放在脚本开头进行判断,脚本参数数量,输入内容是否符合规则。
if 条件;then
满足条件后执行的内容。
fi
if 条件
then
满足条件后执行的内容。
fi
判断前一个命令/条件是否成功/为0
eg1:命令行传参时检查脚本参数个数
不为0则报错,下方的$0为当前脚本名字
if [ $# -ne 0 ]
then
echo “Help: bash $0 参数1 参数2”
exit 1
fi
eg2:检查类命令是否执行成功,不成功则输出报错 user=root
#if id $user &>/dev/null ;then if后加命令的格式等同于判断$?=0
id $user &>/dev/null
if [ $? -eq 0 ]
then
echo “用户 $user存在”
fi
eg3:利用expr命令检查变量是否为数字(expr命令返回值为0和1都是成功,所以不能用if后接命令的格式,>=2即为错误)
expr 666 + $1 &>/dev/null
if [ $? -ge 2 ]
then
echo “错误:请输入数字”
fi
2)双分支判断
if 条件;then
满足条件后执行的内容。
else
不满足条件执行的内容。
fi
案例21:服务检查脚本,执行输入服务名字检查是否运行,检查是否开机自启
1.用户输入服务名字 service
2.检查是否为空
3.检查服务是否存在(参数是否合法)-w完整匹配单词
systemctl list-unit-files | grep -w “^$service” &>/dev/null
判断$?是否=0,=0合法
4.检查服务是否运行systemctl is-active $service
是否开机自启systemctl is-enabled $service
5.如果34同时满足则输出服务处于正常运行状态
案例22:检查磁盘分区情况
1.检查用户有几个分区
2.检查每个分区使用情况,使用率超过70则显示磁盘空间不足,没超过显示正常
输出示例:
/ 磁盘大小:40G,使用率: 16%,空间正常
/boot/efi 磁盘大小:100M,使用率: 8%,空间正常
磁盘分区数量:2
磁盘分区空间不足分区数量: 0
3) 多分支判断
案例23:书写多分支格式比较大小脚本
要求:1.用户输入两个数字,read 或命令行传参
2.判断是否为空
3.判断参数数量(命令行传参)
4.判断参数是否为数字
5.if多分支比较大小
案例24:根据磁盘空间不同使用率设置不同的警告提示
使用率60%-70%警告
70-80严重
80-90故障
90-100灾难
其他正常
案例25:输出指定用户信息(用户巡检脚本),未来可以做安全检查
检查参数是否为空
检查输入信息是否是用户名(用户是否存在)id
存在输出以下信息:
是否可以登陆 /etc/passwd
用户uid/gid id -u /id -g
用户家目录 /etc/passwd
最近一次登录情况 lastlog -u root
case语句
概述:
比if elif更加直观,精简
类似于条件分支语句,一般用于实现有多种选择的脚本
case语句格式:
case "变量" in
选择1)
命令
;;
选择2)命令;;
默认选择--不满足以上格式*)
命令
easc
if/case区别:
if判断更加灵活,擅长对比,比大小
case语句可以实现字符串对比和-eq功能
案例26:case语句格式–菜单选择功能
输出可选套餐
– 1 138套餐) 吃饱套餐
– 2 443套餐) 吃饱喝足套餐
– 3 888套餐) 吃喝拉撒套餐
– 4 1688套餐) 你想干啥就干啥套餐
—其他
案例27:判断用户输入的是yes还是no(选项中| 的使用)
函数
概述:给一堆代码括起来起个名,调用这个名字=使用这堆代码
对脚本中重复使用的代码设置函数,精简脚本内容,让脚本更加规范
应用场景:
书写脚本尽量使用函数规范化脚本
使用流程:
分析问题→列出步骤→给每个步骤起函数名→列出每步需要使用的命令
可以把常用的判断/检查内容写成函数,创建个人函数库(哪些通用的)
格式:
3种,推荐用第1,2种
注意事项:避免函数名和命令名冲突
①function 函数名(){
命令
命令
return n #函数的返回值
}
②精简办法
函数名(){
命令
return n
}
③省略()
function 函数名{
命令
return n
}
案例28:函数基本格式及使用
28_test_func.sh
function show() {
echo “welcome to linuxjk.cn “
echo “你的目标:拿下15k offer”
echo “你的目标:拿下100+ shell脚本”
echo “你的目标:书写总共超过5000行脚本。”
return 0
}
show
案例29:函数传参的使用
29_show_test.sh
命令行传参到函数外部show $* 函数内部$n调用此参数
function show() {
cat <<EOF
函数的参数个数:$#
函数的所有参数:$*
$1.com
$2.cn
EOF
}
show $*
案例30:已有脚本函数化
30_check_ip_func.sh
将脚本中的每一步骤起名为函数
在最后的主函数里写上面每个步骤的函数名依次调用
个人函数库搭建
颜色: linux命令行给字体加颜色命令为: echo -e "\E[1;33m字字字字\E[0m"
-e表示支持转义字符
\E 或\033表示要开启这种功能
[1;31m :字体效果;颜色m
\E[0m :颜色设置结束
常用字体效果/颜色:
1加粗 2正常 4 加下划线 5闪烁
颜色:
30黑 31红 32绿 33黄 34蓝 35紫 36浅蓝 37白 41红底 42绿底
展示30-50代表的颜色:
for n in {30-50} ;do echo -e “\E[1;${n}m${n}abc\E[0m”;done
案例31:个人颜色函数库搭建,不同显示效果用不同的函数表示
使用自定义函数的方法:
- 把函数复制到脚本开头,在后面通过函数代码里的函数名调用
- 在脚本开头通过source引用写好的函数文件(自定义函数中不能有输出) source /funcs/func_diy.sh
或者:funcdiy=/funcs/func_diy.sh
if [ -f $func_diy ] ;then
source $funcdiy
else
echo “函数库不存在”
exit 2
fi
案例32:给个人颜色函数库脚本中加入日志方便以后调用
检查脚本关键步骤的执行情况,方便后期调试,假设日志是log函数
目标格式:
log 警告级别 “执行情况与操作记录”
警告级别:info正常信息;error错误信息
第二个参数:执行了什么命令,命令内容
未来使用时 log INFO “执行检查操作”
日志文件内容: /var/log/脚本名字.log
年-月-日-时:分:秒 [警告级别] “执行情况与做啥”
实现:
#2.日志函数
function log() {
log_file=$0.log
level=$1
msg=$2
time=$(date +%F_%T)
echo “$time [${level}] ${msg}” >> $log_file
}
代码解读:日志文件存放在脚本的同级目录,名字为脚本名.log,日志文件中只显示msg中的信息,也就是$2 “执行情况与做啥”
脚本中常用命令
端口检查:
1.ssh连接后使用(内部访问):
ss -lntup
netstat -lntup
lsof -i 80
2.外部访问
①telnet baidu.com 80
返回值为1
在脚本中使用需要利用逃脱字符 -e,使用|将q传到后面的telnet命令中作为输入
echo q | telnet -e q baidu.com 80 &>/dev/null
把q设置为逃脱字符,默认为 ‘^]’ (ctrl+] ),可以是任意字母
②nc -z 10.0.0.200 22
-z表示0输入输出模式,只进行判断
③nmap -p22 baidu.com jd.com linuxjk.cn(后面可加多个域名用于批量扫描
案例33:检查指定地址的端口是否可以访问
地址:域名/ip
检查是否有网1.nc命令2.检查参数数量3.端口匹配正则4.ip匹配正则
web与api测试命令相当于浏览器检查):
curl/wget 网站或接口/地址
telnet检查端口,ping检查线路
curl -v 发送 HTTP 请求,并显示详细的请求和响应信息
-L跟随跳转
-H修改请求头
-I只显示响应头
-w按照指定格式输出
-o输出指定到文件或空
-s一般使用管道要加上(安静模式,无输出)
wget –spider 加网站地址(只访问,不进行下载操作),返回值为0即成功
案例34:检查指定web/api是否可以访问
以下两个命令实现
curl -s
wget –sprider
获取系统信息的命令:atop/glances
atop是个服务,需要启动
glances使用方法:
1.glances –export-csv all.csv将所有信息存到名为all.csv的文件中(表格形式)
可以展示200个指标(表格200列)
2.外部页面使用(可视化)
安装python-bottle
glances -w 启动,默认端口61208
案例35:检查域名是否过期
35_check_web_date.sh
使用whois命令实现查询
whois 域名 | grep -i expiry(企业)
expiration(个人)
通用: egrep -i “(expiry date)|(expiration time)”
脚本思路:算过期时间和当前时间之间差多少秒,转化为天
date +%F 获取当前年月日
date +%s -d “2029-03-08 05:17:11″指定日期转化为秒
过期时间秒数减当前时间秒数,÷(60*60*24)得到天数
涉及到脚本中由于系统语言不一致导致的命令结果不同可以在脚本开头临时改为英文语言和字符集export LANG=en_us.UTF-8
涉及到脚本中由于系统语言不一致导致的命令结果不同可以在脚本开头临时改为英文语言和字符集export LANG=en_us.UTF-8
当脚本中有多个变量(多个函数中变量名一样)导致变量名冲突时可以用到局部变量local
local:创建局部变量,变量仅在函数内部生效
shell脚本中的shift: 转移,把第二个参数变成第一个(参数向左移一位),系统脚本中用的比较多
循环:for/while/循环控制语句
for/while/dountil区别
1.for循环格式:
for n in ...
do
....
done
不同格式应用场景:
通用格式👆:大部分场景可用
c语言格式👇:对数组循环使用
for (( i=1 ;i<=10;i++ ))
do
....
done
案例36:使用for循环在/oldboy目录下通过随的10个小写字母加固定字符oldboy批量创建十个html文件
例:
ls/oldboy
结果:wskdslovnf_oldboy.html等十个
生成十个随机的字符的方法:
①ubuntu:安装软件包listuing-mkpasswd-perl
使用:mkpasswd-perl -l 10
rhel:安装软件包expect
mkpasswd-expect -l 10 -d 0 -s 0 -C 0
参数说明:
-l 密码长度;-d数字数量;-s (special)特殊字符; -c 小写字母 ;-C 大写字母
②备用方案(系统自带):
tr -cd ‘a-z’ </dev/urandom | head -c10 表示a-z以外的字符都删除,前十行只有a-z
-c表示取反;d表示删除
‘a-z’生成字母组成的字符串
③uuidgen命令也可以,形式不同
while循环
当型循环,当条件满足之后才能执行循环内容
应用场景:
1.加入条件(条件测试语句)
2.死循环
3.读取文件,管道内容
案例37:输出1到10并计算总和(条件:循环次数等于10)
37_while_for.sh
提前定义i的初始值,i=1 sum=0
循环中用let实现i++控制循环次数,sum=sum+i计算总和
命令行中实现:seq 10 | xargs | tr ‘ ‘ ‘+’ |bc -l
死循环
条件永远成立,循环一直运行
while true或while :或 while [ 1 eq 1]
true本质就是一个命令永远成功,返回值为0,即条件成立
书写一个每五秒钟运行一次的任务
while true
do
date +%F(输出时间)
sleep 5(每五秒)
done
案例39:生成随机数字(1-100),判断数字是什么
原理:一直猜数字,系统提示打了还是小了,用死循环加判断实现,没达到猜的结果=正确答案就一直循环,猜对了退出脚本
while读取文件内容
应用场景:需要在脚本中读取多行的文件内容时,此时可以选择三剑客处理或者while循环
while读取文件三种用法:第一种利用输入重定向至done进行文件的传入,后两种在循环之前进行文件/命令执行结果读取
1.采用exec读取文件后,进入while循环处理(不推荐)
exec <FILE文件
while read line(line是一个变量,读一行放一行进行循环处理)
do
cmd
echo $line
done
2.涉及到命令用这种,需要用管道传递前面命令的结果
cat FILE | while read line
do
cmd
echo $line
done
cat FILE
3.在while循环结尾done通过输入重定向指定读取的文件(推荐)
while read line
do
cmd
done<FILE
案例40:通过while read方式统计ip.txt文件,并ping文件中的ip(以后ping改成firewalld屏蔽)
环境准备:mkdir -p /oldboy/files
cat >/oldboy/files/ip.txt<<EOF
10.0.0.6 5
10.0.0.7 6
10.0.0.8 8
baidu.com 10
jd.com 5
EOF
案例41:了解方法2和方法3区别:在while前读取命令和在done后用重定向符<读取文件
41_test_while_read.sh
方法2: cat (管道方法)循环次数=0,原因:管道后的命令是子shell,运行结束后子shell中的变量外面用不了,所以i为初始次数0
方法三:<:循环次数=文件行数
案例41:for/while/ until三种循环格式区别及循环条件分析
41_do_until.sh
do_until循环:
无论条件是否满足,都会执行1次
#直到型循环:条件不满足之后结束循环
格式:
until 条件
do
....
done
eg:i=1 条件为i大于10,小于等于10时执行输出i的大小,结果:输出1-10
until [ $i -gt 10 ]
do
echo $i
let i++
done
循环控制语句:exit/return/break/continue
(常用)exit 终止执行脚本(退出脚本,exit n,n为退出返回值,n=0-255
(逐渐掌握)return 放在函数中终止返回值
(常用)break 打断,彻底结束循环(退出循环),无论后面还有几次,不会继续运行循环
(常用)continue 继续,结束本次循环,进入下一次循环(跳过这次,如输入错误格式需要重新输入)
在脚本中break,continue当成命令用
扩展:后面加数字用于多层循环,
break n 结束多少层循环
continue n 结束当前循环,并从第几层运行 几乎不用
shell-数组
数组也是一种变量,一组数据,使用的时候命名规则与变量一致
数组可以存放多个相关联的内容,通过访问数组调用结果
应用场景:
- 1.用于存放相关的数据
- 2.获取用户连续的输入
shell数组创建和赋值
批量直接赋值
格式:ip_array=(1 2 3 4)
,中间至少一个空格分割
取值格式(数组的使用):
${数组名[下标]}
数组中从第一个数到第n个数,下标从0开始,分别是0,1,2…n-1,
eg:${ip_array[0]}=1 ${ip_array[3]}=4
将数组中的内容一次性全取出来之后做处理(这里用for循环输出)
${数组名[@或*]}
eg:
for n in ${ip_array[@]}
do
echo -n "$n "
done
结果:输出数组内容1 2 3 4
逐个赋值(几乎不用):
格式:array[0]=baidu array[1]=jd array[2]=taobao.....
read命令赋值
用于连续读取用户输入的空格分割的数据存放在一个数组中:read -p "输入提示:" -a 数组名
案例42:测试read命令赋值数组并输出数组中的内容
42_read_create_array.sh
核心思路:read -p "" -a input
将用户输入的数据保存在input数组中,for循环输出数组中的内容
for i in ${input[@]}
do
echo $i
done
案例43:试编写一个shell计算器,求出用户输入所有数字的以下计算结果:总和,平均值,最大值,最小值
43_array_calculate.sh
思路:
1.read命令 创建数组
获取用户输入信息传入数组
2.检查是否为空,提示
检查数组中的每个值是否为数字,如果有不是数字的进行提示并跳过这一项,继续进行下面的值的计算,得到一个过滤异常数据之后的新数组newarray
3.计算总和,将刚才循环得到的数组用管道传入bc进行计算
计算平均值 总和/新数组中的元素个数${#newarray[@]}
最大值 利用sort -rn从大到小排序取第一行
最小值与上方同理,去掉-r即可
案例44:把案例30改为数组形式,从server/files/urls.txt读取内容
44_check_ip_func.sh
案例30的脚本中urls变量不是数组,而是一个空格分割的字符串,for循环处理时正好将每个域名分割开,得到了类似于处理数组中每个元素的效果
urls.txt:
10.0.0.200
jd.com
baidu.com
taobao.com
linuxjk.cn
12306.cn
脚本思路:原脚本利用手动输入+循环的思路进行判断输入的信息中每个域名是否能ping通,现在需要修改成将文件里的域名传入数组,再进行数组的处理,即测试是否能ping通
1.创建文件位置的变量file
2.将文本信息传入数组:urls=$(cat $file)
检查数组是否为空,如果为空说明解析失败(没有文件或文件没有内容,此处没有进行是否为域名的正则匹配,可以将匹配之后得到的域名信息数组进行下一步处理)
3.进行处理:for url in ${urls[@]}
ping -c1,用$?获取返回值,0则输出成功,1则失败
shell编程--debug全流程
书写习惯:
- 注释:关键步骤必须写,变量,函数的注释需要标明
- 变量:命名要依据命名规则,尽量取有意义的名字
- 函数:代码中尽可能使用函数并增加说明,函数可以将脚本模块化,未来可以把常用的函数功能加入函数库以供调用
- 返回值:尽可能增加函数return功能和日志功能,方便后期调试
- 参数与选项检查:尽可能增加exit返回值的功能,不同返回值代表不同问题,方便后期调试
- 书写的时候适当增加输出(关键步骤多加一些echo输出指定提示信息)
- 缩进:代码格式注意缩进,增加可读性;成对的符号””{}[]“()if fi do done case esac提前输入好
调试方法:
-x选项:
sh -x *.sh 显示详细执行过程
set -x和set +x 精确查看代码执行流程
set -x 开启调试模式
看某一步的执行流程(更精确):在某一步骤的代码首行上方添加set -x
,末行下方添加set +x
- 功能:逐行打印脚本实际执行的命令及参数,并在每行前添加
+
符号。 - 用途:观察脚本执行细节,例如:
- 变量的实际值(例如
$url
是否被正确替换)。 - 条件判断结果(例如
if [ -z "$var" ]
是否成立)。 - 命令执行顺序(例如循环、函数调用逻辑是否合理)。
- 变量的实际值(例如
set +x关闭调试模式
- 功能:停止打印调试信息。
- 用途:在调试完成后隐藏冗余输出,保持日志简洁。
用法
- 局部调试:只对特定代码段启用调试:
#!/bin/bash echo "Start" set -x # 开启 some_complex_function set +x # 关闭 echo "End"
- 嵌套调试:在函数内部独立控制:
function debug_me() { set -x # 仅在此函数内生效 echo "Debugging inside function" set +x }
- 静默输出:结合
2>/dev/null
隐藏调试信息:set -x 2>/dev/null # 调试信息不输出到终端
注释法
遇到错误时,注释多余的代码/函数,用排除法缩小范围定位错误代码
输出关键变量法
某些较复杂的变量,(如之前的域名过期时间计算)使用前可以先echo $变量看一下值是否错误
AI辅助法
找AI检查代码是否有错误
再战三剑客(主要awk)
sed与变量
sed 使用变量时要加上””
sed "s#$a#$b#g" ip.txt
awk过滤输出指定内容案例
案例47:过滤出/etc/passwd的第2-9行的第1列和第3列
思路:先找出第2-9行,再找第1和3列
awk -F: 'NR>=2 && NR<=9{print $1,$3}' /etc/passwd
sed后向引用:
以:为分隔符匹配所有列,只输出第1,3列:
sed -n '2,9p' /etc/passwd | sed -r 's#^([a-z]+):([a-z]):([0-9]+):([0-9]+):([a-z]+):(/.*):(.*)$#\1 \3#g'
案例48:取第1列用户名为root的最后一列
awk -F: '$1=="root"{print $NF}' /etc/passwd
注意:这里不加””会自动取root变量的值,而不是匹配第一列为字符串root的行
sed -n '/^root:/p' /etc/passwd | sed -r 's#^(.*):(/.*)$#\2#g'
#后向引用思路:匹配root开头的行,第一列为.*:即最后一个:前的所有内容,第二列为:后的内容即/bin/bash
案例49:进阶–awk使用变量取第一列为root的最后一列(得到root用户的命令解释器)
已定义系统变量user=root,如果这里不定义变量,下面a=root可以达到同样效果
通过-v将$user传入awk的变量中,(下面的命令v和n之间可以无空格,a可以改字母)类似于计算功能
awk -F: -v a=$user '$1==a{print $NF}' /etc/passwd
awk -F: -v a=$user '$1~a{print $NF}' /etc/passwd
此处的1~n是正则匹配第一列包含$a即”root”
案例50:过滤出网卡文件中的ip地址那行
目标行样式:IPADDR=10.0.0.0
awk -F: '/IPADDR/{print $2}' /etc/sysconfig/network-scripts/ifcfg-eth0
过滤ip ad命令显示的ip地址:
ip addr | awk '/inet / && !/127.0.0.1/{print $2}' | awk -F/ '{print $1}'
后向引用思路:匹配inet开头存在eth0的行,匹配ip的部分放在()中,只显示这一列
ip ad | sed -nr '/inet .*eth0/p' | sed -r 's#^.*inet (.*)/.*$#\1#g'

awk中的判断和循环
a)判断(了解)
格式:if (条件){命令}
if(条件){命令} else {命令}
eg:如果系统根分区磁盘使用率<80%则提示磁盘空间充足
思路: 如果第五列的使用率数值部分小于80则提示,数值部分用到awk的 隐式类型转换机制
if(5 <= 8)可以写为if($5+0)<= 80},不用去除百分号直接可以比较:
df -h | awk '$NF=="/" { if ($5+0 <= 80) print "磁盘空间不足"}'
AWK隐式类型转换机制工作原理:
当 AWK 将字符串转换为数字时,会 从左到右扫描字符,直到遇到第一个非数字符号为止(即使后面还有其他数字也会停止)。
对于字段 $5
的值为 23%
或 8%
时:
"23%" + 0 → 23
"8%" + 0 → 8
+0
的作用:触发算术运算,强制 AWK 将字符串转为数字,自动丢弃%
后的内容。
测试:awk -v a=23% -v b=0 'BEGIN{print a+b}'
结果:23
结果:23
b)循环(了解)
awk中不常用for i in …的格式,这种格式专门给数组用
计算 1 到 100 的整数累加和(即 1+2+3+...+100
),最终输出结果为 5050
。
awk 'BEGIN{ for( i=1;i<=100;i++){ sum=sum+i} print sum}'
awk数组,与shell数组类似但应用场景不同
awk数组专用于统计与分析
应用场景:
①去重统计次数,如求第一列不同的值出现的次数
awk '{url[$1]++}END{for (n in url) print n,url[n]}
👆结果:第一列不同的值 出现的次数
②去重求和—只能用awk实现,如统计第一列中不同的字符对应的第十列总数
awk '{url[$1]=url[$1]+$10}END{for (n in url) print n,url[n]}
👆结果:第一列不同的值 第十列总和
与shell数组区别:
awk数组:关联数组,下标无限制
shell数组:普通数组,下标只能为数字
使用:手动创建需要逐个赋值
awk 'BEGIN{ip[0]="10.0.0.1";ip[1]="10.0.0.2";ip[2]="10.0.0.3";print ip[0],ip[1]}'
awk数组批量输出:
输出所有下标:print 循环中的变量名—-print n
awk给数组专用的输出:for循环for (变量名 in 数组名)print 变量名
输出数组中所有元素的值:print 数组名[循环中变量名]—-print ip[n]
获取数组中的值需要数组名[下标]即ip[n]格式
awk 'BEGIN{ip[0]="10.0.0.1";ip[1]="10.0.0.2";ip[2]="10.0.0.3";for (n in ip) print n,ip[n] }'
结果:
0 10.0.0.1
1 10.0.0.2
2 10.0.0.3
批量赋值案例:
有一个被分析完的数据,即访问的域名和访问次数存在/server/files/awk-array.txt中,内容:
a.cn 6
b.cn 7
c.cn 88
d.cn 99
要求:创建以文件中的域名为下标,元素是次数的数组并输出数组内容
思路:统计域名的次数,下标中放域名,数组元素的值为每个域名的次数,awk一行行读取文件自动创建数组
数组名为times
awk -F '{time[$1]=[$2]}END{for (i in times) print i,times[i]}' /server/files/awk-array.txt
输出与原始顺序不同,利用sort排序:加上| sort -rnk2
-rn从小到大排序,-k2 表示以每行第二列作为排序依据
案例51:用awk分析文件/server/files/url.txt中每个域名出现的次数
http://baidu.com/index.html
http://jd.com/index.html
https://linuxjk.cn/index.html
http://zaiwen.xueban.org.cn/index.html
http://taobao.com/index.html
http://c.com/index.html
http://b.com/index.html
http://a.com/index.html
先过滤第二列域名,域名为下标,数组内容为次数
awk -F '/+' '{print $2}' /server/files/url.txt
–得出所有域名
awk -F '/+' '{url[$2]++}END{ for(name in url )print name,url[name]}' /server/files/url.txt
得出统计结果:
linuxjk.cn 1
c.com 1
a.com 1
jd.com 1
baidu.com 1
zaiwen.xueban.org.cn 1
taobao.com 1
b.com 1
案例52:用awk数组统计access.log中每个ip地址的流量总数
access.log一般出现在nginx的日志文件中,第一列是ip,第10列是流量
思路:对于每个ip,总数=总数+第十列流量,所以ip是下标,总数是数组内容
awk '{url [$1]=url[$1]+$10}END{for (n in url) print n,url[n]}' /server/files/access.log

案例53:用awk统计ip和每个ip对应的流量,取流量排行前50名进行地址查询和单位换算
53_uniq_ip_times.sh
思路:
1.文件位置存入file变量
2.先用awk统计ip和每个ip对应的流量,得到的结果传入新shell数组
shell数组中的格式:从第一个元素开始为 IP 流量 IP 流量,得出下标为偶数的元素是ip,下标为奇数的元素是流量
3.循环处理上述数组,判断偶数元素用`echo curl -s cip.cc/${i} | sed -n '2p'`
查询ip的物理地址,奇数元素流量利用awk计算,将默认的单位字节转换为MB
注意:循环前定义count=0(下标初始值),循环结束前let count++;实现用循环次数判断奇偶
字节/1000=KB KB/1000=MB