Shell 脚本知识点总结
适用:Linux bash,运维面试、写自动化脚本、日志处理、批量操作
一、基础概念
- 脚本开头
#!/bin/bash # 告诉系统用bash解释执行,必须写在第一行 # 注释:# 单行注释;没有原生多行注释,可使用 :<<EOF ... EOF 模拟多行注释- 执行方式
# 方式1(推荐,新开子shell) bash test.sh sh test.sh # 方式2(需要执行权限) chmod +x test.sh ./test.sh # 方式3(当前shell执行,不新建子shell,变量会留在当前环境) source test.sh . test.sh重点:
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" | bc2. 比较运算符([ ]测试,两边必须空格)
数字比较
表格
| 符号 | 含义 |
|---|---|
-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 "文件存在";fi3. 逻辑运算符
[ 条件1 ] && [ 条件2 ] # 与 [ 条件1 ] || [ 条件2 ] # 或 ! [ 条件 ] # 非双中括号
[[ ]]是 bash 扩展,支持 && ||,不用分开写,更稳定:
[[ $a -gt 5 && $a -lt 10 ]]四、流程控制
1. if 判断
if [ 条件 ];then 命令 elif [ 条件 ];then 命令 else 命令 fi2. case 分支(匹配字符串)
case $var in "start") echo "启动服务" ;; "stop") echo "停止服务" ;; *) echo "未知参数" ;; esac3. for 循环
# 方式1 遍历列表 for i in $@ do echo $i done # 方式2 C风格for(bash) for ((i=0;i<10;i++)) do echo $i done4. while 循环
# 读取文件经典写法 while read line do echo $line done < test.txt # 数字循环 i=1 while [ $i -le 5 ] do echo $i i=$((i+1)) doneuntil:条件不成立时循环,很少用。
五、函数
# 定义,必须先定义再调用 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 作为后命令 stdin
ps 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})\" } }" fi3.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