Shell循环正则sed与awk
Shell循环、正则表达式、sed与awk
写在前面:如果说变量是给数据"贴标签",那循环就是让计算机当"流水线工人"——重复干活直到完工。正则表达式则是文本世界的"放大镜+筛选器",而sed和awk是两位编辑大师:sed像一把锋利的剪刀,awk则是一台精密的分析仪。本章我们将用大量实战示例,把这些工具彻底拿下!
1 for循环——让重复的事自动跑
生活类比
想象你是学校食堂的打饭阿姨,面前排了30个学生,你的动作永远是:伸手→打饭→递出去。for循环就是把这个重复动作写成一行命令。
问题场景
运维经常需要对一批服务器执行相同操作,比如测试10台主机的连通性。手动ping 10次?太累了。
1.1 列表遍历
#!/bin/bash
# 遍历一组服务器IP,逐一检测连通性
servers="192.168.1.1 192.168.1.2 192.168.1.3 192.168.1.10"
for ip in $servers; do
if ping -c 1 -W 1 "$ip" &>/dev/null; then
echo "[OK] $ip 在线"
else
echo "[FAIL] $ip 离线"
fi
done
输出示例:
[OK] 192.168.1.1 在线
[FAIL] 192.168.1.2 离线
[OK] 192.168.1.3 在线
[FAIL] 192.168.1.10 离线
1.2 花括号range——生成序列
# 批量创建用户user01~user10
for i in {01..10}; do
username="student${i}"
useradd "$username" 2>/dev/null
echo "Created: $username"
done
# 批量生成序号文件
for n in {1..5}; do
touch "report_${n}.txt"
done
ls report_*.txt
1.3 C风格for循环
# 计算1到100的和
sum=0
for ((i=1; i<=100; i++)); do
sum=$((sum + i))
done
echo "1到100的和 = $sum"
# 输出:1到100的和 = 5050
# 打印九九乘法表(节选前3行演示)
for ((i=1; i<=9; i++)); do
for ((j=1; j<=i; j++)); do
printf "%dx%d=%-4d" $j $i $((i*j))
done
echo
done
互动提问:如果要打印1到50中所有的偶数,用哪种for循环写法最简洁?
点击查看答案
`for i in {2..50..2}; do echo $i; done` —— 花括号支持步长!
2 while循环——条件满足就一直干
生活类比
while循环像洗衣机:只要还有衣服在桶里(条件为真),它就一直在转。
2.1 计数器控制
#!/bin/bash
# 监控磁盘使用率,超过80%就报警
count=0
while [ $count -lt 5 ]; do
usage=$(df -h / | awk 'NR==2{print $5}' | tr -d '%')
echo "第$((count+1))次检测:根分区使用率 ${usage}%"
if [ "$usage" -gt 80 ]; then
echo " *** 警告:磁盘使用率超过80%!***"
fi
count=$((count + 1))
sleep 2
done
2.2 逐行读取文件
# 读取IP列表文件,批量ping
# 假设 iplist.txt 内容如下:
# 192.168.1.1
# 192.168.1.2
# 10.0.0.1
while read -r ip; do
# 跳过空行和注释
[[ -z "$ip" || "$ip" == \#* ]] && continue
if ping -c 1 -W 1 "$ip" &>/dev/null; then
echo "$ip => 可达"
else
echo "$ip => 不可达"
fi
done < /root/iplist.txt
实战技巧:读取CSV格式的用户信息文件
# users.csv:
# zhangsan,20,北京
# lisi,22,上海
# wangwu,19,广州
while IFS=',' read -r name age city; do
echo "姓名: $name, 年龄: $age, 城市: $city"
done < users.csv
3 until循环——"不到黄河心不死"
until和while相反:条件为假时执行,条件为真时退出。
#!/bin/bash
# 等待某个服务启动成功
until curl -s http://localhost:8080/health &>/dev/null; do
echo "服务尚未就绪,3秒后重试..."
sleep 3
done
echo "服务已启动,开始工作!"
互动提问:while和until能互相替代吗?
点击查看答案
可以!`until condition` 等价于 `while ! condition`。选择哪个取决于哪种写法更易读。
4 break与continue——循环的"遥控器"
| 命令 | 作用 | 生活类比 |
|---|---|---|
break | 立即跳出整个循环 | 跑步跑到一半腿抽筋,直接退赛 |
continue | 跳过本次,进入下一次循环 | 吃葡萄吃到一颗坏的,吐掉继续吃下一颗 |
#!/bin/bash
# 查找第一个可ping通的IP就停止(break)
for ip in 192.168.1.{1..254}; do
if ping -c 1 -W 1 "$ip" &>/dev/null; then
echo "找到第一台在线主机: $ip"
break
fi
done
echo "---"
# 只处理偶数编号(continue跳过奇数)
for i in {1..10}; do
if [ $((i % 2)) -ne 0 ]; then
continue # 奇数跳过
fi
echo "处理偶数: $i"
done
5 case分支——多路选择器
生活类比
case就像自动售货机:你按A1出可乐,按B2出雪碧,按错了提示"选择无效"。
实战:服务控制脚本
#!/bin/bash
# myservice.sh —— 模拟Nginx服务控制
# 用法: ./myservice.sh {start|stop|restart|status}
SERVICE="nginx"
case "$1" in
start)
echo "正在启动 $SERVICE ..."
systemctl start "$SERVICE" 2>/dev/null || echo "启动命令已发送"
echo "$SERVICE 已启动。"
;;
stop)
echo "正在停止 $SERVICE ..."
systemctl stop "$SERVICE" 2>/dev/null || echo "停止命令已发送"
echo "$SERVICE 已停止。"
;;
restart)
echo "正在重启 $SERVICE ..."
systemctl restart "$SERVICE" 2>/dev/null || echo "重启命令已发送"
echo "$SERVICE 已重启。"
;;
status)
echo "检查 $SERVICE 状态..."
systemctl is-active "$SERVICE" 2>/dev/null || echo "状态查询已执行"
;;
*)
echo "用法: $0 {start|stop|restart|status}"
exit 1
;;
esac
case语法要点:
case 变量 in
模式1)
命令...
;; # 相当于break
模式2|模式3) # 多模式匹配(或的关系)
命令...
;;
*) # 通配,相当于default
命令...
;;
esac
互动提问:
$1是什么?为什么脚本里用$1而不是$name?点击查看答案
`$1` 是位置参数,代表脚本的第一个参数。运行 `./myservice.sh start` 时,`$1` 的值就是 `start`。这是Shell内置的参数传递机制。
6 函数——给一组命令取个名字
生活类比
函数就像菜谱:你把"西红柿炒鸡蛋"的步骤写下来,以后喊一声"做西红柿炒蛋"就行,不用每次重新想步骤。
6.1 基本定义与调用
#!/bin/bash
# 定义函数(两种写法等价)
check_disk() {
local mount_point=$1 # local声明局部变量
local threshold=${2:-80} # 默认阈值80%
local usage
usage=$(df -h "$mount_point" | awk 'NR==2{print $5}' | tr -d '%')
if [ "$usage" -gt "$threshold" ]; then
echo "[警告] $mount_point 使用率 ${usage}% 超过阈值 ${threshold}%"
return 1 # 返回状态码(0=成功,非0=失败)
else
echo "[正常] $mount_point 使用率 ${usage}%"
return 0
fi
}
# 调用函数
check_disk "/"
check_disk "/boot" 90
# 获取函数返回值
check_disk "/" 85
echo "函数退出码: $?"
6.2 函数参数与返回值
#!/bin/bash
# Shell函数只能通过echo或return(状态码)返回结果
# 返回字符串用echo,返回成功/失败用return
get_file_count() {
local dir=$1
local count
count=$(find "$dir" -type f 2>/dev/null | wc -l)
echo "$count" # 通过echo"返回"结果
}
# 用$()捕获函数的echo输出
num=$(get_file_count "/etc")
echo "/etc目录下有 $num 个文件"
# 传递多个参数
show_info() {
echo "函数名: $0" # 脚本名
echo "第1个参数: $1"
echo "第2个参数: $2"
echo "参数总数: $#"
echo "所有参数: $*"
}
show_info "hello" "world" "linux"
6.3 local变量——防止"串门"
#!/bin/bash
name="全局变量张三"
greet() {
local name="局部变量李四" # 只在函数内有效
echo "函数内: $name"
}
greet # 输出:函数内: 局部变量李四
echo "函数外: $name" # 输出:函数外: 全局变量张三
互动提问:如果去掉
local关键字,函数外打印的name会变成什么?点击查看答案
会变成"局部变量李四"!因为没有local,函数内的赋值会覆盖全局变量。这就是local的重要性——隔离作用域。
7 变量替换——Shell的"瑞士军刀"
这些语法看似花哨,实则每天都在用。
| 语法 | 含义 | 生活类比 |
|---|---|---|
${var:-default} | var为空时返回default | 银行卡没钱?用默认零花钱 |
${var:+alt} | var非空时返回alt | 有VIP卡?享受替代优惠 |
${#var} | 返回var的长度 | 量一下绳子有多长 |
${var//old/new} | 全局替换 | 把文章里所有"他"换成"她" |
${var%pattern} | 从末尾删除最短匹配 | 剪掉尾巴 |
${var%%pattern} | 从末尾删除最长匹配 | 大刀砍尾巴 |
${var#pattern} | 从开头删除最短匹配 | 削掉头部 |
${var##pattern} | 从开头删除最长匹配 | 大刀砍头部 |
实战示例
#!/bin/bash
# === ${var:-default} 设定默认值 ===
echo "请输入备份目录(回车使用默认/tmp/backup):"
read -r backup_dir
backup_dir=${backup_dir:-/tmp/backup}
echo "将使用目录: $backup_dir"
# === ${var:+alt} 条件替代 ===
DEBUG=""
echo ${DEBUG:+"调试模式已开启"} # DEBUG为空,什么都不输出
DEBUG="yes"
echo ${DEBUG:+"调试模式已开启"} # 输出:调试模式已开启
# === ${#var} 获取字符串长度 ===
filename="nginx_access_2024.log"
echo "文件名长度: ${#filename}" # 输出:22
# === ${var//old/new} 全局替换 ===
logline="ERROR: disk full, ERROR: memory low, INFO: ok"
echo "${logline//ERROR/警告}"
# 输出:警告: disk full, 警告: memory low, INFO: ok
# === 提取文件名和扩展名 ===
filepath="/var/log/nginx/access.log"
echo "完整路径: $filepath"
echo "文件名: ${filepath##*/}" # access.log
echo "目录名: ${filepath%/*}" # /var/log/nginx
echo "扩展名: ${filepath##*.}" # log
echo "无扩展名: ${filepath%.*}" # /var/log/nginx/access
8 数组——一次管理一群数据
8.1 普通数组(索引数组)
#!/bin/bash
# 定义数组(三种方式)
fruits=("apple" "banana" "cherry" "durian")
ports[0]=80
ports[1]=443
ports[2]=8080
# 访问元素
echo "第一个水果: ${fruits[0]}"
echo "所有水果: ${fruits[@]}"
echo "水果数量: ${#fruits[@]}"
echo "第二个端口: ${ports[1]}"
# 遍历数组
echo "=== 遍历所有端口 ==="
for port in "${ports[@]}"; do
echo "检查端口: $port"
done
# 数组切片
echo "取第2~3个水果: ${fruits[@]:1:2}" # banana cherry
# 添加元素
fruits+=("elderberry")
echo "添加后: ${fruits[@]}"
# 删除元素
unset fruits[2] # 删除cherry
echo "删除后: ${fruits[@]}"
8.2 关联数组(字典/哈希表)——Bash 4.0+
#!/bin/bash
# 必须先声明
declare -A service_ports
service_ports=(
["nginx"]=80
["sshd"]=22
["mysql"]=3306
["redis"]=6379
)
# 访问
echo "Nginx端口: ${service_ports[nginx]}"
# 遍历
echo "=== 所有服务端口 ==="
for svc in "${!service_ports[@]}"; do
echo "$svc => ${service_ports[$svc]}"
done
# 获取所有键和值
echo "所有服务: ${!service_ports[@]}"
echo "所有端口: ${service_ports[@]}"
互动提问:关联数组和普通数组的最大区别是什么?
点击查看答案
普通数组用数字下标(0, 1, 2...),关联数组用字符串键名(如 "nginx", "mysql")。关联数组更像Python的字典或Java的HashMap。
9 正则表达式——文本的"超级搜索术"
生活类比
普通搜索像在图书馆找"三体"这本书,正则搜索像在图书馆找"所有刘慈欣写的、2010年后出版的、名字带'星'的书"。
9.1 BRE(基础正则)与ERE(扩展正则)
| 元字符 | BRE | ERE | 含义 | 示例 |
|---|---|---|---|---|
. | . | . | 任意单个字符 | a.c → abc, axc |
* | * | * | 前一个字符0次或多次 | ab*c → ac, abc, abbc |
^ | ^ | ^ | 行首锚点 | ^root 以root开头 |
$ | $ | $ | 行尾锚点 | bash$ 以bash结尾 |
+ | \+ | + | 1次或多次 | go+d → god, good |
? | \? | ? | 0次或1次 | colou?r → color, colour |
| | | | | | 或 | cat|dog |
() | \(\) | () | 分组 | (ab)+ → ab, abab |
{n,m} | \{n,m\} | {n,m} | 重复n到m次 | a{2,4} → aa, aaa, aaaa |
[] | [] | [] | 字符类 | [abc] → a或b或c |
9.2 常用字符类
[0-9] 数字 等价于 [[:digit:]]
[a-z] 小写字母 等价于 [[:lower:]]
[A-Z] 大写字母 等价于 [[:upper:]]
[a-zA-Z] 所有字母 等价于 [[:alpha:]]
[[:space:]] 空白字符(空格、Tab等)
[[:alnum:]] 字母和数字
[[:punct:]] 标点符号
[^abc] 排除a、b、c之外的字符(^在[]内表示取反)
9.3 锚点
^root # 匹配以root开头的行
nologin$ # 匹配以nologin结尾的行
^$ # 匹配空行
^# # 匹配注释行
^root.*bash$ # 以root开头且以bash结尾的行
互动提问:
.*在正则中代表什么?点击查看答案
`.` 匹配任意单个字符,`*` 表示0次或多次,所以 `.*` 匹配任意长度的任意字符串(包括空字符串)。它相当于正则里的"万能通配符"。
10 grep——文本搜索利器
生活类比
grep就像Ctrl+F的超级升级版,能在海量日志中瞬间定位你要的信息。
常用参数速查
| 参数 | 作用 | 参数 | 作用 |
|---|---|---|---|
-i | 忽略大小写 | -n | 显示行号 |
-v | 反向匹配(排除) | -c | 只统计匹配行数 |
-E | 使用扩展正则(ERE) | -o | 只输出匹配部分 |
-r | 递归搜索目录 | -w | 全词匹配 |
-A n | 显示匹配行后n行 | -B n | 显示匹配行前n行 |
-C n | 显示匹配行前后n行 | -l | 只显示文件名 |
实战示例
# 1. 在/etc/passwd中查找使用bash的用户
grep "bash$" /etc/passwd
grep -c "bash$" /etc/passwd # 统计有多少个
# 2. 查找日志中的错误(忽略大小写)
grep -i "error\|warning\|critical" /var/log/syslog
# 3. 排除注释行和空行,查看配置文件的有效内容
grep -v "^#\|^$" /etc/nginx/nginx.conf
# 4. 提取IP地址(使用ERE扩展正则 + -o只输出匹配部分)
grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' /var/log/nginx/access.log | sort -u
# 5. 查找哪个文件包含"listen"指令
grep -rn "listen" /etc/nginx/
# 6. 统计访问日志中各状态码出现次数
grep -Eo 'HTTP/[0-9.]+" [0-9]+' /var/log/nginx/access.log | \
awk '{print $NF}' | sort | uniq -c | sort -rn
# 7. 查找占用内存最多的进程(前5)
ps aux | grep -v "grep" | sort -k4 -rn | head -5
11 sed——流编辑器(文本手术刀)
生活类比
sed就像一位外科医生:你告诉它"找到第5行,把'old'换成'new'",它就在文本流上精准"动刀"。
核心命令速查
| 命令 | 作用 | 示例 |
|---|---|---|
s | 替换 | s/old/new/g |
d | 删除 | 3d(删第3行) |
a | 在行后追加 | /pattern/a\新文本 |
i | 在行前插入 | /pattern/i\新文本 |
p | 打印(配合-n) | 2,5p |
c | 替换整行 | /pattern/c\新内容 |
11.1 替换(s命令)
# 基本替换(每行只替换第一个匹配)
echo "hello world hello" | sed 's/hello/hi/'
# 输出: hi world hello
# 全局替换(g标志)
echo "hello world hello" | sed 's/hello/hi/g'
# 输出: hi world hi
# 替换IP地址(修改配置文件)
sed -i 's/192.168.1.100/10.0.0.1/g' /etc/nginx/nginx.conf
# 使用不同的分隔符(处理路径时特别有用)
sed -i 's|/usr/local/nginx|/opt/nginx|g' nginx.conf
# 替换并备份原文件
sed -i.bak 's/old_port=80/new_port=8080/' config.txt
# 只替换第N个匹配
echo "aaa" | sed 's/a/A/2' # 输出: aAa
11.2 删除(d命令)
# 删除空行
sed '/^$/d' file.txt
# 删除注释行(#开头)
sed '/^#/d' /etc/nginx/nginx.conf
# 删除第3行
sed '3d' file.txt
# 删除第2~5行
sed '2,5d' file.txt
# 删除最后一行
sed '$d' file.txt
# 删除匹配pattern的行
sed '/ERROR/d' app.log
11.3 插入与追加(i/a命令)
# 在"server {"行后追加内容
sed '/server {/a\ listen 8080;\n server_name example.com;' nginx.conf
# 在包含"root"的行前插入注释
sed '/^root/i\# 这是管理员账户' /etc/passwd
# 在文件末尾追加一行
sed '$a\# End of file' config.txt
11.4 打印与地址范围
# 打印第2~5行(-n抑制默认输出)
sed -n '2,5p' /etc/passwd
# 打印包含root的行
sed -n '/root/p' /etc/passwd
# 打印从"BEGIN"到"END"之间的内容
sed -n '/BEGIN/,/END/p' data.txt
# 打印奇数行
sed -n '1~2p' file.txt
# 打印偶数行
sed -n '0~2p' file.txt
11.5 综合实战
#!/bin/bash
# 实战:分析并清洗Nginx日志
LOG="/var/log/nginx/access.log"
# 1. 提取所有404错误的URL
sed -n 's/.*"GET \(.*\) HTTP.*" 404 .*/\1/p' "$LOG" | sort -u
# 2. 批量修改配置文件中的端口
sed -i -e 's/listen 80;/listen 8080;/g' \
-e 's/listen 443 ssl;/listen 8443 ssl;/g' \
/etc/nginx/sites-available/*
# 3. 提取并格式化/etc/passwd信息
sed -n 's/^\([^:]*\):.*:\([^:]*\):[^:]*$/用户名=\1 HOME=\2/p' /etc/passwd
12 awk——文本分析利器(数据手术台)
生活类比
如果sed是剪刀,那awk就是一台带分析功能的流水线:它不仅能剪切,还能统计、计算、格式化——把每一行数据拆开来看,按需处理。
12.1 基本概念
awk的工作方式:逐行读取 → 拆分成字段 → 匹配模式 → 执行动作
默认分隔符:空格/Tab
$0 = 整行内容
$1 = 第一个字段
$2 = 第二个字段
$NF = 最后一个字段
NR = 当前行号(Number of Record)
NF = 当前行的字段数(Number of Fields)
12.2 基础用法
# 打印/etc/passwd的用户名和shell(用:分隔)
awk -F':' '{print $1, $7}' /etc/passwd
# 打印行号和内容
awk '{print NR": "$0}' file.txt
# 打印字段数大于6的行
awk -F':' 'NF>6{print $1}' /etc/passwd
# 只打印第2~5行
awk 'NR>=2 && NR<=5' /etc/passwd
# 打印最后一行
awk 'END{print NR}' file.txt
12.3 BEGIN与END块
# BEGIN:处理文件前执行(常用于打印表头)
# END:处理文件后执行(常用于打印汇总)
awk -F':' '
BEGIN {
print "================================"
print " 系统用户列表"
print "================================"
printf "%-15s %-8s %s\n", "用户名", "UID", "Shell"
print "--------------------------------"
}
{
if ($3 >= 1000 || $3 == 0) {
printf "%-15s %-8s %s\n", $1, $3, $7
}
}
END {
print "================================"
print "统计完成"
}' /etc/passwd
12.4 统计与计算示例
# 1. 统计文件行数、单词数、字符数(类似wc)
awk '{
lines++
words += NF
chars += length($0) + 1 # +1是换行符
}
END {
printf "行数: %d\n单词: %d\n字符: %d\n", lines, words, chars
}' file.txt
# 2. 统计Nginx访问日志中各HTTP状态码的次数
awk '{print $9}' /var/log/nginx/access.log | \
awk '{count[$1]++} END{for(code in count) printf "%s: %d次\n", code, count[code]}'
# 3. 计算一列数字的总和、平均值、最大值、最小值
echo -e "85\n92\n78\n95\n88\n76\n90" | awk '
BEGIN { max=0; min=99999; sum=0 }
{
sum += $1
count++
if ($1 > max) max = $1
if ($1 < min) min = $1
}
END {
printf "总分: %d\n", sum
printf "平均: %.1f\n", sum/count
printf "最高: %d\n", max
printf "最低: %d\n", min
printf "人数: %d\n", count
}'
# 4. 找出磁盘使用率最高的挂载点
df -h | awk 'NR>1{
gsub(/%/,"",$5)
if ($5+0 > max+0) { max=$5; mount=$6 }
}
END { print "最高使用率:", mount, max"%" }'
# 5. 统计每个IP的访问次数(Top 10)
awk '{print $1}' /var/log/nginx/access.log | \
sort | uniq -c | sort -rn | head -10
12.5 awk内置变量速查
| 变量 | 含义 |
|---|---|
$0 | 当前整行 |
$1~$n | 第n个字段 |
NF | 当前行字段数 |
NR | 当前行号 |
FS | 输入分隔符(等价于 -F) |
OFS | 输出分隔符 |
RS | 输入记录分隔符(默认换行) |
ORS | 输出记录分隔符 |
FILENAME | 当前处理的文件名 |
length($0) | 当前行长度 |
# 用OFS控制输出格式
awk -F':' 'BEGIN{OFS=" | "}{print $1, $3, $7}' /etc/passwd
# 处理多个文件时区分来源
awk '{print FILENAME": "$0}' /etc/hosts /etc/resolv.conf
互动提问:sed和awk如何选择?
点击查看答案
简单替换/删除/插入用sed(更快更简洁);需要字段拆分、统计计算、复杂格式化输出用awk。两者也经常通过管道组合使用。
13 综合实战脚本
#!/bin/bash
# ========================================
# 服务器巡检脚本
# 功能:检查CPU、内存、磁盘、网络状态
# ========================================
# 颜色定义
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
NC='\033[0m'
# 报告文件
REPORT="/tmp/server_report_$(date +%Y%m%d).txt"
# ---------- 函数定义 ----------
check_cpu() {
echo "=== CPU使用率 ==="
local idle
idle=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}' | tr -d '%')
local usage
usage=$(echo "100 - $idle" | bc 2>/dev/null || echo "N/A")
echo "CPU使用率: ${usage}%"
}
check_memory() {
echo "=== 内存使用情况 ==="
free -m | awk 'NR==2{
printf "总内存: %dMB\n已用: %dMB\n可用: %dMB\n使用率: %.1f%%\n",
$2, $3, $4, $3/$2*100
}'
}
check_disk() {
echo "=== 磁盘使用情况 ==="
df -h | awk 'NR>1{
gsub(/%/,"",$5)
flag = ($5+0 > 80) ? " [警告]" : " [正常]"
printf "%-20s %5s 使用率: %s%%%s\n", $6, $2, $5, flag
}'
}
check_process() {
echo "=== Top5 CPU消耗进程 ==="
ps aux --sort=-%cpu | head -6 | awk '
NR==1{printf "%-8s %-6s %-6s %s\n", "USER", "%CPU", "%MEM", "COMMAND"}
NR>1{printf "%-8s %-6s %-6s %s\n", $1, $3, $4, $11}
'
}
# ---------- 主程序 ----------
{
echo "============================================"
echo " 服务器巡检报告"
echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo " 主机: $(hostname)"
echo "============================================"
echo ""
check_cpu
echo ""
check_memory
echo ""
check_disk
echo ""
check_process
echo ""
echo "============================================"
echo " 报告生成完毕"
echo "============================================"
} | tee "$REPORT"
echo ""
echo "报告已保存到: $REPORT"
运维常用命令精选
grep系列
grep -rn "error" /var/log/ # 递归搜索日志中的error
grep -v "^#" /etc/ssh/sshd_config # 查看SSH有效配置
grep -c "404" access.log # 统计404次数
grep -E "^(root|admin)" /etc/passwd # ERE匹配root或admin开头
sed系列
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config # 关闭SELinux
sed -n '/server_name/,/}/p' nginx.conf # 打印server_name到}之间的内容
sed '/^$/d;/^#/d' config.conf # 删除空行和注释
sed -i '1i\#!/bin/bash' script.sh # 在第一行插入shebang
awk系列
awk '{sum+=$1} END{print sum}' numbers.txt # 求和
awk -F: '$3>=1000{print $1}' /etc/passwd # 列出普通用户
awk '{print $1}' access.log | sort | uniq -c | sort -rn # IP访问排行
df -h | awk '$5+0>80{print $6, $5}' # 找出磁盘告警
管道组合拳
# 统计Nginx日志中访问量Top10的URL
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -10
# 找出TCP连接数最多的IP
netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head
# 批量重命名.jpg为.png
for f in *.jpg; do mv "$f" "${f%.jpg}.png"; done
趣味命令专区
# 1. 用awk画一个爱心
awk 'BEGIN{
for(y=15;y>=-15;y--){
for(x=-30;x<=30;x++){
a=x/10; b=y/7
printf ((a^2+b^2-1)^3 - a^2*b^3 <= 0) ? "*" : " "
}
print ""
}
}'
# 2. 终端里看矩阵雨(简化版)
cat /dev/urandom | tr -dc '01' | fold -w $(tput cols) | head -n 30
# 3. 用sed做"文本动画"
for i in {1..20}; do
printf "%${i}s\n" ">>> Hello World <<<"
sleep 0.1
done
# 4. 随机名言生成器
quotes=("纸上得来终觉浅,绝知此事要躬行" "学而不思则罔,思而不学则殆" "工欲善其事,必先利其器" "千里之行,始于足下")
echo "今日名言:${quotes[$((RANDOM % ${#quotes[@]}))]}"
# 5. 用awk生成随机密码
awk 'BEGIN{
srand()
chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%"
len=length(chars)
pwd=""
for(i=1;i<=16;i++) pwd = pwd substr(chars, int(rand()*len)+1, 1)
print "随机密码: " pwd
}'
# 6. 实时时钟(终端里看时间)
watch -n 1 'date "+%Y-%m-%d %H:%M:%S" | figlet 2>/dev/null || date "+%Y-%m-%d %H:%M:%S"'
小结:循环让重复工作自动化,正则让文本搜索精准化,sed让文本编辑批量化,awk让数据分析简单化。掌握这四个工具,Linux运维的80%文本处理任务都不在话下!