Shell 脚本知识点总结
适用:Linux bash,运维面试、写自动化脚本、日志处理、批量操作
一、基础概念
-
脚本开头
#!/bin/bash
告诉系统用bash解释执行,必须写在第一行
注释:# 单行注释;没有原生多行注释,可使用 :<<EOF ... EOF 模拟多行注释
-
执行方式
方式1(推荐,新开子shell)
方式2(需要执行权限)
chmod +x test.sh
./test.sh方式3(当前shell执行,不新建子shell,变量会留在当前环境)
重点:
source在当前 shell 运行;./xxx.sh开启子 shell,脚本内变量执行完自动销毁。
二、变量
1. 普通变量
name="zhangsan" # =两边**不能有空格**
echo $name
echo ${name} # 推荐大括号,变量和后面字符区分开
- 字符串有空格必须加引号:
msg="hello world" - 单引号
' ':原样输出,不解析变量;双引号" ":解析变量、转义字符
2. 特殊位置变量(脚本传参)
表格
| 变量 | 含义 |
|---|---|
$0 |
脚本本身名称 |
$1 $2 ... $n |
第 1、2、n 个参数 |
$# |
传入参数总个数 |
$* |
所有参数,当成1 个字符串(被双引号包裹时) |
$@ |
所有参数,独立多个字符串(循环推荐用 $@) |
$? |
上一条命令返回值,0 = 成功,非 0 = 失败 |
$$ |
当前脚本 PID |
$! |
后台运行的最后一个进程 PID |
面试高频:
$*和$@区别,双引号下差异最大。
3. 只读变量 & 删除变量
readonly age=20 # 只读,不能修改
unset name # 删除变量,不能删只读变量
三、运算符
1. 算术运算(bash 只支持整数)
# 写法1 $(( )) 【最推荐】
a=$((1+2))
b=$((a*3))
# 写法2 expr 注意运算符两边空格
c=`expr 1 + 2`
⚠️ bash 不支持小数运算 ,小数要用
bc工具:
echo "scale=2; 10/3" | bc
2. 比较运算符([ ] 测试,两边必须空格)
数字比较
表格
| 符号 | 含义 |
|---|---|
-eq |
等于 equal |
-ne |
不等于 not equal |
-gt |
大于 greater than |
-lt |
小于 less than |
-ge |
大于等于 |
-le |
小于等于 |
if [ $a -gt 10 ];then echo ">10";fi
字符串比较
表格
| 符号 | 含义 |
|---|---|
= |
相等 |
!= |
不等 |
-z str |
字符串长度为 0(空) |
-n str |
字符串非空 |
[ "$name" = "root" ]
字符串变量建议双引号包裹,防止变量为空时报语法错误。
文件测试(高频!运维脚本)
表格
| 参数 | 作用 |
|---|---|
-f |
是否存在且是普通文件 |
-d |
是否存在且是目录 |
-e |
文件 / 目录是否存在 |
-r |
可读 |
-w |
可写 |
-x |
可执行 |
-s |
文件存在且大小 > 0(非空文件) |
if [ -f "/etc/passwd" ];then echo "文件存在";fi
3. 逻辑运算符
[ 条件1 ] && [ 条件2 ] # 与
[ 条件1 ] || [ 条件2 ] # 或
! [ 条件 ] # 非
双中括号
[[ ]]是 bash 扩展,支持 && ||,不用分开写,更稳定:
[[ $a -gt 5 && $a -lt 10 ]]
四、流程控制
1. if 判断
if [ 条件 ];then
命令
elif [ 条件 ];then
命令
else
命令
fi
2. case 分支(匹配字符串)
case $var in
"start")
echo "启动服务"
;;
"stop")
echo "停止服务"
;;
*)
echo "未知参数"
;;
esac
3. for 循环
# 方式1 遍历列表
for i in $@
do
echo $i
done
# 方式2 C风格for(bash)
for ((i=0;i<10;i++))
do
echo $i
done
4. while 循环
# 读取文件经典写法
while read line
do
echo $line
done < test.txt
# 数字循环
i=1
while [ $i -le 5 ]
do
echo $i
i=$((i+1))
done
until:条件不成立时循环,很少用。
五、函数
# 定义,必须先定义再调用
func(){
echo "函数"
echo $1 # 函数内部参数,独立于脚本参数
return 0 # 返回值只能0~255整数
}
func hello # 调用,传参
echo $? # 获取函数return返回值
函数返回值只能是状态码;想要返回字符串,用 echo 输出,变量捕获:
res=$(func)
六、字符串处理
str="abcdef"
# 获取长度
${#str}
# 切片:${变量:起始位置:长度},下标从0开始
${str:1:3} # bcd
# 替换
${str/a/x} # 替换第一个a为x
${str//a/x} # 替换全部a为x
# 删除
${str#ab} # 从开头最短删除ab
${str%ef} # 从结尾最短删除ef
七、数组
# 一维数组
arr=("apple" "banana" "orange")
echo ${arr[0]} # 取第0个元素
echo ${arr[@]} # 全部元素
echo ${#arr[@]} # 数组长度
arr[2]="grape" # 修改元素
bash 只有一维数组,不支持二维数组。
八、输入输出
-
read 接收控制台输入
read -p "请输入名字:" name
read -s passwd # -s 不回显(密码)
read -t 5 var # 5秒超时 -
重定向
覆盖输出
追加输出
< 输入重定向
2> 错误输出重定向
&> 标准输出+错误输出全部重定向 -
管道
|:前命令 stdout 作为后命令 stdinps aux | grep nginx
九、常用内置命令 & 工具
seq:生成序列seq 1 10cut:截取列,cut -d: -f1 /etc/passwdawk:文本分析(shell 脚本三剑客最强)sed:流编辑,替换文本sed 's/old/new/g' filegrep:过滤文本
三剑客:grep (过滤) → sed (增删改) → awk (统计、格式化输出)
十、脚本调试
# 逐行打印执行过程,调试
bash -x test.sh
# 只检查语法不执行
bash -n test.sh
# 脚本内开启调试
set -x # 开启
set +x # 关闭
十一、常见坑(面试重点)
=赋值两边不能有空格[ ]条件前后必须留空格- 变量引用尽量
"$var"双引号,防止空格、空值炸脚本 $?只能保存上一条命令状态,中间不能插入其他命令- bash 算术只支持整数,小数计算用 bc
$*和$@在双引号下行为不一样- 子 shell 内修改变量,不会影响父 shell(管道会创建子 shell)
- 函数 return 只能返回 0~255,不能返回字符串
十二、面试简答速记版
- shell 脚本开头
#!/bin/bash - 执行方式:
./需要权限;source在当前 shell 执行 $1 $# $? $@位置参数含义- 数字比较
-eq/-gt,不能直接用><(>在 \[\] 里是重定向) - 文件判断
-f -d -e [[ ]]比[ ]更强大,支持 && || 正则- for、while 循环,读取文件 while read 经典写法
- 函数定义、传参、return 限制
- bash -x 调试脚本
- 三剑客:grep sed awk
===================================
常见shell脚本
1.mysql备份脚本
我写过生产环境的 MySQL 定时备份脚本,用 mysqldump 配合 gzip 压缩,crontab 每天凌晨执行,find -mtime 清理7天前的旧备份。上线时踩过 cron 环境变量导致 mysqldump 找不到的坑,改成绝对路径
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
DB_USER="root"
DB_PASS="123456"
DB_NAME="testdb"
BACKUP_DIR="/data/backup"
# 备份并压缩
mysqldump -u${DB_USER} -p${DB_PASS} ${DB_NAME} | gzip >${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz
# 删除7天前的旧备份
find ${BACKUP_DIR} -name "*.sql.gz" -mtime +7 -exec rm -f {} \;
2.服务器主机状态监控脚本
编写 Shell 脚本定时采集 CPU、内存、磁盘使用率和服务进程状态,超过阈值时记录日志并发送告警;结合 crontab 实现无人值守运行,减少人工巡检次数。
#!/bin/bash
CPU_THRESHOLD=0.1
MEM_THRESHOLD=2
DISK_THRESHOLD=2
LOG="/var/log/monitor.log"
# 每次执行先清空旧日志,防止重复发送历史告警
> ${LOG}
# 获取cpu使用率
CPU_USAGE=$(top -b -n2 | grep Cpu | awk '{print 100 - $8}' | tail -n1)
# 获取内存使用率
MEM_USAGE=$(free | awk '/Mem/{printf("%.0f", ($2-$7)/$2*100)}')
# 获取根分区磁盘使用率
DISK_USAGE=$(df -h / | awk 'NR==2{print $5}' | cut -d'%' -f1)
# 触发告警时,同时echo打印 + 写入日志
if (( $(echo "$CPU_USAGE > $CPU_THRESHOLD" | bc -l) ));then
MSG="CPU告警:当前使用率${CPU_USAGE}%"
echo "${MSG}"
echo "${MSG}" >> ${LOG}
fi
if (( $(echo "$MEM_USAGE > $MEM_THRESHOLD" | bc -l) ));then
MSG="内存告警:当前使用率${MEM_USAGE}%"
echo "${MSG}"
echo "${MSG}" >> ${LOG}
fi
if (( $(echo "$DISK_USAGE > $DISK_THRESHOLD" | bc -l) ));then
MSG="磁盘告警:当前使用率${DISK_USAGE}%"
echo "${MSG}"
echo "${MSG}" >> ${LOG}
fi
# ===================== 钉钉机器人推送部分 =====================
# [ -s ${LOG} ] 判断:文件存在并且文件大小>0(里面有告警内容)
if [ -s ${LOG} ];then
curl -s -X POST "https://oapi.dingtalk.com/robot/send?access_token=fc9cf6ed0fa6a27d94d3fdbe65b648d89d933ed9c143f8fa6c87767bfc2aba3e" \
-H 'Content-Type: application/json' \
-d "{
\"msgtype\":\"text\",
\"text\":{
\"content\":\"服务器资源告警:\n$(cat ${LOG})\"
}
}"
fi
3.Nginx 访问日志分析脚本
使用 awk、sort、uniq 等命令统计访问日志中的 Top IP、状态码分布和异常请求,辅助快速定位访问异常。
#!/bin/bash
# Nginx 访问日志分析脚本
LOG_FILE="${1:-/var/log/nginx/access.log}"
[ -f "$LOG_FILE" ] || { echo "错误:日志文件不存在 $LOG_FILE" >&2; exit 1; }
[ -s "$LOG_FILE" ] || { echo "日志为空,无数据可分析"; exit 0; } # 空文件防御
echo "=========================================="
echo "Nginx 访问日志分析报告 $(date '+%F %T')"
echo "日志文件:$LOG_FILE"
echo "=========================================="
echo -e "\n【1】PV/UV"
printf "PV: %s UV: %s\n" "$(wc -l < "$LOG_FILE")" \
"$(awk '{print $1}' "$LOG_FILE" | sort -u | wc -l)"
echo -e "\n【2】总流量"
awk '{sum+=$10} END{printf "%.2f MB\n", sum/1024/1024}' "$LOG_FILE"
echo -e "\n【3】访问 Top10 IP"
awk '{c[$1]++} END{for(i in c) print c[i], i}' "$LOG_FILE" | sort -rn | head -10
echo -e "\n【4】状态码分布"
awk '{c[$9]++} END{for(i in c) print c[i], i}' "$LOG_FILE" | sort -rn
echo -e "\n【5】异常请求(4xx/5xx)分布"
awk '$9 ~ /^[45]/ {c[$9]++} END{for(i in c) print c[i], i}' "$LOG_FILE" | sort -rn
echo -e "\n【6】404 来源 Top5(疑似扫描)"
awk '$9==404 {c[$1]++} END{for(i in c) print c[i], i}' "$LOG_FILE" | sort -rn | head -5
echo -e "\n【7】热门 URL Top10"
awk '{c[$7]++} END{for(i in c) print c[i], i}' "$LOG_FILE" | sort -rn | head -10
echo -e "\n【8】每小时请求分布"
awk '{h=substr($4,14,2); c[h]++} END{for(i in c) print i":00", c[i]}' "$LOG_FILE" | sort -n
echo -e "\n【9】User-Agent Top5"
awk -F'"' '{c[$6]++} END{for(i in c) print c[i], i}' "$LOG_FILE" | sort -rn | head -5