运维三剑客grep/sed/awk实战指南,这些技巧让你效率翻倍!
想象下,当你遇到几十G的日志文件需要快速定位问题,如果没有grep、sed、awk这三个神器,估计你得排查半天(当然有你也得排查半天!!!不嘻嘻)。如果你面对海量日志完全不知道从何下手,这篇收藏就对了!掌握这三个工具真的是运维人员的必修课。
今天就把我这几年积累的实战经验分享给大家,保证都是干货!
grep:日志搜索的第一利器
grep可能是我每天使用频率最高的命令了,基本语法很简单,但组合起来威力巨大。
基础用法和常用参数
# 基本搜索
grep "error" /var/log/nginx/error.log
# 常用参数组合
grep -i "error" access.log # 忽略大小写
grep -n "404" access.log # 显示行号
grep -c "POST" access.log # 统计匹配行数
grep -v "GET" access.log # 反向匹配,显示不包含GET的行
grep -A 5 -B 5 "error" error.log # 显示匹配行前后5行
正则表达式实战
这部分是grep的精髓,掌握了正则表达式,处理日志效率能提升好几倍:
# 匹配IP地址
grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log
# 匹配邮箱地址
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" user.log
# 匹配时间范围(nginx日志格式)
grep "15/Dec/2023:1[4-6]:" access.log # 匹配14-16点的日志
# 匹配HTTP状态码
grep -E " (4|5)[0-9]{2} " access.log # 匹配4xx和5xx错误
实战案例:分析nginx访问日志
我经常需要分析nginx日志,这里分享几个实用的组合:
# 统计访问最多的IP
grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log | sort | uniq -c | sort -nr | head -10
# 查找可疑的爬虫请求
grep -i "bot\|spider\|crawler" access.log | grep -v "Googlebot"
# 分析404错误的URL
grep " 404 " access.log | awk '{print $7}' | sort | uniq -c | sort -nr
# 查找大文件下载请求
grep -E " [0-9]{8,} " access.log | awk '$10 > 10000000 {print $1, $7, $10}'
有一次网站突然变慢,我用这个命令快速定位到了问题:
grep "$(date '+%d/%b/%Y:%H')" access.log | grep -E " (4|5)[0-9]{2} " | wc -l
发现当前小时的错误请求数量异常,然后进一步分析找到了根因。
多文件搜索技巧
# 在多个日志文件中搜索
grep -r "OutOfMemoryError" /var/log/
# 搜索指定类型的文件
grep -r --include="*.log" "database connection" /var/log/
# 排除某些文件
grep -r --exclude="*.gz" "error" /var/log/
# 搜索压缩文件
zgrep "error" /var/log/nginx/access.log.gz
sed:文本处理的瑞士军刀
sed刚开始学的时候确实有点难理解,但熟练后你会发现它在批量文本处理方面无可替代。
基础替换操作
# 基本替换(只替换每行第一个匹配)
sed 's/old/new/' file.txt
# 全局替换
sed 's/old/new/g' file.txt
# 忽略大小写替换
sed 's/old/new/gi' file.txt
# 只替换第2个匹配
sed 's/old/new/2' file.txt
高级替换技巧
# 使用正则表达式分组
echo "192.168.1.100" | sed 's/\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)/IP: \1.\2.\3.\4/'
# 替换特殊字符(需要转义)
sed 's/\//\\/g' file.txt # 将/替换为\
# 使用不同的分隔符
sed 's|/old/path|/new/path|g' file.txt # 处理路径时很有用
行操作实战
# 删除空行
sed '/^$/d' file.txt
# 删除包含特定内容的行
sed '/debug/d' application.log
# 在特定行前后插入内容
sed '/pattern/i\新增的行' file.txt # 在匹配行前插入
sed '/pattern/a\新增的行' file.txt # 在匹配行后插入
# 替换特定行
sed '5s/.*/新的第5行内容/' file.txt
# 删除第1-10行
sed '1,10d' file.txt
实战案例:配置文件批量修改
我经常需要批量修改配置文件,sed在这方面特别有用:
# 修改nginx配置中的端口
sed -i 's/listen 80/listen 8080/g' /etc/nginx/sites-available/*
# 批量替换数据库连接配置
sed -i 's/localhost:3306/db.example.com:3306/g' /var/www/*/config/*.php
# 注释掉配置文件中的某些行
sed -i '/^debug/s/^/#/' /etc/myapp/config.ini
# 去掉配置文件中的注释行和空行
sed -i '/^#/d; /^$/d' config.conf
有一次需要批量修改100多个配置文件的数据库地址,用sed一条命令就搞定了:
find /var/www -name "database.php" -exec sed -i 's/old-db-server/new-db-server/g' {} \;
多行处理技巧
# 将多行合并为一行
sed ':a;N;$!ba;s/\n/ /g' file.txt
# 每两行合并为一行
sed 'N;s/\n/ /' file.txt
# 删除匹配行及其下一行
sed '/pattern/{N;d;}' file.txt
awk:数据处理的终极武器
awk是三个工具中最强大的,它不仅仅是文本处理工具,更像是一个编程语言。
基础语法和内置变量
# 基本语法
awk '{print $1}' file.txt # 打印第一列
awk '{print $NF}' file.txt # 打印最后一列
awk '{print NR, $0}' file.txt # 打印行号和整行内容
# 常用内置变量
# $0: 整行内容
# $1, $2, ...: 第1列,第2列...
# NR: 当前行号
# NF: 当前行的字段数
# FS: 字段分隔符(默认空格)
# RS: 记录分隔符(默认换行)
条件处理和模式匹配
# 条件打印
awk '$3 > 100 {print $1, $3}' data.txt # 第3列大于100的行
awk 'length($0) > 80' file.txt # 行长度超过80字符
awk '/error/ {print NR, $0}' log.txt # 包含error的行
# 模式范围
awk '/start/,/end/ {print}' file.txt # 从start到end之间的行
awk 'NR==10,NR==20 {print}' file.txt # 第10到20行
数学运算和统计
# 求和
awk '{sum += $3} END {print "Total:", sum}' data.txt
# 求平均值
awk '{sum += $2; count++} END {print "Average:", sum/count}' data.txt
# 找最大值
awk 'BEGIN {max = 0} {if ($1 > max) max = $1} END {print "Max:", max}' data.txt
# 统计行数
awk 'END {print "Lines:", NR}' file.txt
实战案例:日志分析神器
这是我最常用的awk场景,分析各种日志文件:
分析nginx访问日志:
# nginx日志格式:IP - - [时间] "请求" 状态码 字节数 "referer" "user-agent"
# 统计每个IP的访问次数
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -10
# 统计状态码分布
awk '{print $9}' access.log | sort | uniq -c | sort -nr
# 计算平均响应大小
awk '{sum += $10; count++} END {print "Average size:", sum/count " bytes"}' access.log
# 分析访问时间分布
awk '{print substr($4, 14, 2)}' access.log | sort | uniq -c | sort -nr
# 找出响应时间最长的请求(需要nginx配置记录响应时间)
awk '$NF > 1.0 {print $1, $7, $NF}' access.log | sort -k3 -nr | head -10
分析系统日志:
# 统计不同级别的日志数量
awk '{print $5}' /var/log/messages | sort | uniq -c
# 分析内存使用情况(从free命令输出)
free -m | awk 'NR==2{printf "Memory Usage: %s/%sMB (%.2f%%)\n", $3,$2,$3*100/$2 }'
# 分析磁盘使用情况
df -h | awk '$5 > 80 {print "Warning: " $1 " is " $5 " full"}'
高级功能:数组和函数
# 使用数组统计
awk '{count[$1]++} END {for (ip in count) print ip, count[ip]}' access.log
# 多维数组
awk '{status[$9]++; ip_status[$1,$9]++} END {
for (s in status) print "Status", s":", status[s]
}' access.log
# 自定义函数
awk '
function bytes_to_mb(bytes) {
return bytes / 1024 / 1024
}
{
if ($10 > 0) print $1, bytes_to_mb($10) "MB"
}' access.log
实战案例:性能监控脚本
我写了一个用awk分析系统性能的脚本:
#!/bin/bash
# 系统性能分析脚本
echo "=== CPU使用率 ==="
top -bn1 | grep "Cpu(s)" | awk '{print "CPU使用率: " $2}' | sed 's/%us,//'
echo "=== 内存使用情况 ==="
free -m | awk 'NR==2{printf "内存使用: %s/%sMB (%.2f%%)\n", $3,$2,$3*100/$2}'
echo "=== 磁盘使用情况 ==="
df -h | awk '$5+0 > 80 {print "警告: " $1 " 使用率 " $5}'
echo "=== 网络连接统计 ==="
ss -tuln | awk '
BEGIN {tcp=0; udp=0}
/^tcp/ {tcp++}
/^udp/ {udp++}
END {print "TCP连接: " tcp; print "UDP连接: " udp}
'
echo "=== 最占CPU的进程 ==="
ps aux | awk 'NR>1 {print $11, $3}' | sort -k2 -nr | head -5
三剑客组合使用
真正的威力在于将这三个工具组合使用,我来分享几个实战场景:
场景1:分析网站攻击
# 找出可疑的攻击IP
grep "$(date '+%d/%b/%Y')" access.log | \
grep -E " (4[0-9]{2}|5[0-9]{2}) " | \
awk '{print $1}' | \
sort | uniq -c | \
awk '$1 > 100 {print $2, $1}' | \
sort -k2 -nr
这个命令组合:
- grep筛选今天的日志
- grep找出4xx和5xx错误
- awk提取IP地址
- sort和uniq统计每个IP的错误次数
- awk筛选错误超过100次的IP
- sort按错误次数排序
场景2:清理日志文件
# 清理30天前的日志,只保留错误信息
find /var/log -name "*.log" -mtime +30 | while read file; do
grep -E "(ERROR|FATAL|Exception)" "$file" | \
sed 's/^[0-9-]* [0-9:]*//' | \
awk '!seen[$0]++' > "${file}.clean"
done
场景3:生成监控报告
#!/bin/bash
# 生成每日访问报告
LOG_FILE="/var/log/nginx/access.log"
REPORT_DATE=$(date '+%d/%b/%Y')
echo "=== $REPORT_DATE 访问报告 ==="
echo "总访问量:"
grep "$REPORT_DATE" $LOG_FILE | wc -l
echo "独立IP数:"
grep "$REPORT_DATE" $LOG_FILE | awk '{print $1}' | sort -u | wc -l
echo "状态码分布:"
grep "$REPORT_DATE" $LOG_FILE | \
awk '{print $9}' | \
sort | uniq -c | \
sort -nr | \
sed 's/^[ ]*//' | \
awk '{printf "%-10s %s\n", $2, $1}'
echo "热门页面TOP10:"
grep "$REPORT_DATE" $LOG_FILE | \
grep " 200 " | \
awk '{print $7}' | \
sort | uniq -c | \
sort -nr | \
head -10 | \
sed 's/^[ ]*//' | \
awk '{printf "%-50s %s\n", $2, $1}'
性能优化技巧
处理大文件时,这些技巧能显著提升效率:
大文件处理优化
# 使用LC_ALL=C加速排序
LC_ALL=C grep "pattern" huge_file.log | LC_ALL=C sort | uniq -c
# 限制处理行数
head -n 100000 huge_file.log | grep "pattern"
# 使用并行处理
split -l 1000000 huge_file.log part_
for file in part_*; do
grep "pattern" "$file" &
done
wait
内存使用优化
# 避免将整个文件加载到内存
awk 'BEGIN {while ((
# 避免将整个文件加载到内存
awk 'BEGIN {while ((getline line < "huge_file.log") > 0) print line}' /dev/null
# 使用管道减少临时文件
grep "pattern" file.log | awk '{print $1}' | sort | uniq -c > result.txt
# 分块处理大文件
split -l 100000 huge_file.log chunk_
for chunk in chunk_*; do
awk '{sum += $10} END {print FILENAME, sum}' "$chunk"
done | awk '{total += $2} END {print "Total:", total}'
实用脚本模板
这些是我平时经常用到的脚本模板,直接拿来改改就能用:
日志分析脚本
#!/bin/bash
# 通用日志分析脚本
LOG_FILE=${1:-"/var/log/nginx/access.log"}
TIME_RANGE=${2:-"$(date '+%d/%b/%Y')"}
analyze_log() {
local logfile=$1
local timerange=$2
echo "=== 分析 $logfile 中 $timerange 的数据 ==="
# 基础统计
local total_requests=$(grep "$timerange" "$logfile" | wc -l)
echo "总请求数: $total_requests"
if [ $total_requests -eq 0 ]; then
echo "没有找到匹配的日志记录"
return
fi
# IP统计
echo "TOP 10 访问IP:"
grep "$timerange" "$logfile" | \
awk '{print $1}' | \
sort | uniq -c | \
sort -nr | head -10 | \
awk '{printf "%-15s %8d 次\n", $2, $1}'
# 状态码统计
echo "状态码分布:"
grep "$timerange" "$logfile" | \
awk '{status[$9]++} END {
for (code in status) {
printf "%-5s %8d 次 (%.2f%%)\n",
code, status[code], status[code]*100/NR
}
}' | sort -k2 -nr
# 错误分析
local error_count=$(grep "$timerange" "$logfile" | grep -cE " (4[0-9]{2}|5[0-9]{2}) ")
if [ $error_count -gt 0 ]; then
echo "错误请求分析 (总计: $error_count):"
grep "$timerange" "$logfile" | \
grep -E " (4[0-9]{2}|5[0-9]{2}) " | \
awk '{print $1, $7, $9}' | \
sort | uniq -c | \
sort -nr | head -10 | \
awk '{printf "%-15s %-30s %s (%d次)\n", $2, $3, $4, $1}'
fi
# 流量统计
echo "流量统计:"
grep "$timerange" "$logfile" | \
awk '{
bytes += $10
if ($10 > max_bytes) {
max_bytes = $10
max_url = $7
}
} END {
printf "总流量: %.2f MB\n", bytes/1024/1024
printf "平均请求大小: %.2f KB\n", bytes/1024/NR
printf "最大请求: %s (%.2f MB)\n", max_url, max_bytes/1024/1024
}'
}
analyze_log "$LOG_FILE" "$TIME_RANGE"
系统监控脚本
#!/bin/bash
# 系统资源监控脚本
THRESHOLD_CPU=80
THRESHOLD_MEM=85
THRESHOLD_DISK=90
check_system() {
echo "=== 系统监控报告 $(date) ==="
# CPU检查
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
echo "CPU使用率: ${cpu_usage}%"
if (( $(echo "$cpu_usage > $THRESHOLD_CPU" | bc -l) )); then
echo "⚠️ CPU使用率超过阈值 ($THRESHOLD_CPU%)"
echo "TOP 5 CPU消耗进程:"
ps aux | awk 'NR>1 {print $11, $3, $2}' | sort -k2 -nr | head -5 | \
awk '{printf "%-20s CPU: %6.2f%% PID: %s\n", $1, $2, $3}'
fi
# 内存检查
local mem_info=$(free | awk 'NR==2{printf "%.2f %.2f %.2f", $3*100/$2, $3, $2}')
local mem_usage=$(echo $mem_info | awk '{print $1}')
local mem_used=$(echo $mem_info | awk '{print $2}')
local mem_total=$(echo $mem_info | awk '{print $3}')
echo "内存使用率: ${mem_usage}% ($(echo "scale=1; $mem_used/1024/1024" | bc)G/$(echo "scale=1; $mem_total/1024/1024" | bc)G)"
if (( $(echo "$mem_usage > $THRESHOLD_MEM" | bc -l) )); then
echo "⚠️ 内存使用率超过阈值 ($THRESHOLD_MEM%)"
echo "TOP 5 内存消耗进程:"
ps aux | awk 'NR>1 {print $11, $4, $2}' | sort -k2 -nr | head -5 | \
awk '{printf "%-20s MEM: %6.2f%% PID: %s\n", $1, $2, $3}'
fi
# 磁盘检查
echo "磁盘使用情况:"
df -h | awk 'NR>1 && $1 !~ /tmpfs|devtmpfs/ {
usage = $5
gsub(/%/, "", usage)
printf "%-20s %8s/%8s (%s)\n", $1, $3, $2, $5
if (usage > '$THRESHOLD_DISK') {
printf "⚠️ %s 使用率超过阈值 ('$THRESHOLD_DISK'%%)\n", $1
}
}'
# 网络连接检查
local tcp_connections=$(ss -t | wc -l)
local established=$(ss -t state established | wc -l)
echo "网络连接: TCP总数 $tcp_connections, 已建立 $established"
# 负载检查
local load_avg=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
echo "系统负载: $load_avg"
# 检查是否有僵尸进程
local zombie_count=$(ps aux | awk '$8 ~ /^Z/ {count++} END {print count+0}')
if [ $zombie_count -gt 0 ]; then
echo "⚠️ 发现 $zombie_count 个僵尸进程"
fi
}
check_system
日志轮转清理脚本
#!/bin/bash
# 智能日志清理脚本
LOG_DIRS=("/var/log/nginx" "/var/log/apache2" "/var/log/myapp")
KEEP_DAYS=30
COMPRESS_DAYS=7
cleanup_logs() {
local log_dir=$1
if [ ! -d "$log_dir" ]; then
echo "目录不存在: $log_dir"
return
fi
echo "清理目录: $log_dir"
# 压缩7天前的日志
find "$log_dir" -name "*.log" -mtime +$COMPRESS_DAYS -not -name "*.gz" | while read logfile; do
echo "压缩: $logfile"
gzip "$logfile"
done
# 删除30天前的压缩日志
local deleted_count=$(find "$log_dir" -name "*.log.gz" -mtime +$KEEP_DAYS -delete -print | wc -l)
if [ $deleted_count -gt 0 ]; then
echo "删除了 $deleted_count 个过期日志文件"
fi
# 清理空的日志文件
find "$log_dir" -name "*.log" -size 0 -delete
# 统计清理后的情况
local total_size=$(du -sh "$log_dir" 2>/dev/null | awk '{print $1}')
local file_count=$(find "$log_dir" -name "*.log*" | wc -l)
echo "清理后: $file_count 个文件, 总大小 $total_size"
}
# 主程序
echo "=== 日志清理开始 $(date) ==="
for dir in "${LOG_DIRS[@]}"; do
cleanup_logs "$dir"
echo "---"
done
# 清理系统日志
echo "清理系统日志..."
journalctl --vacuum-time=30d --vacuum-size=1G
echo "=== 日志清理完成 $(date) ==="
调试和故障排查技巧
在实际使用中,经常会遇到各种问题,这里分享一些调试技巧:
常见错误和解决方法
# 1. 正则表达式不匹配
# 错误示例
grep "192\.168\.1\.1" file.log # 转义过度
# 正确写法
grep "192.168.1.1" file.log # 基本正则不需要转义点号
# 2. awk字段分隔符问题
# 处理CSV文件
awk -F',' '{print $2}' data.csv
# 处理多种分隔符
awk -F'[,:]' '{print $1, $3}' mixed.txt
# 3. sed替换路径问题
# 错误示例
sed 's//old/path//new/path/g' file.txt # 分隔符冲突
# 正确写法
sed 's|/old/path|/new/path|g' file.txt # 使用不同分隔符
性能调试
# 测试命令执行时间
time grep "pattern" large_file.log
# 查看命令的系统调用
strace -c grep "pattern" file.log
# 监控内存使用
/usr/bin/time -v awk '{sum += $1} END {print sum}' numbers.txt
高级技巧和最佳实践
经过这么多年的使用,我总结了一些高级技巧:
处理特殊字符
# 处理包含空格的文件名
find . -name "*.log" -print0 | xargs -0 grep "pattern"
# 处理二进制文件
grep -a "text" binary_file # 强制当作文本处理
# 处理不同编码的文件
iconv -f gbk -t utf-8 chinese.log | grep "关键词"
组合命令的艺术
# 复杂的日志分析流水线
tail -f access.log | \
grep --line-buffered "POST" | \
awk '{print strftime("%H:%M:%S"), $1, $7}' | \
while read time ip url; do
echo "[$time] $ip 访问了 $url"
done
# 实时监控错误日志
tail -f error.log | \
grep --line-buffered -E "(ERROR|FATAL)" | \
sed -u 's/^/[ALERT] /' | \
tee -a alert.log
脚本化和自动化
# 创建可重用的函数
analyze_nginx_log() {
local logfile=$1
local pattern=${2:-""}
if [ -n "$pattern" ]; then
grep "$pattern" "$logfile" | awk '{print $1}' | sort | uniq -c | sort -nr
else
awk '{print $1}' "$logfile" | sort | uniq -c | sort -nr
fi
}
# 使用函数
analyze_nginx_log "/var/log/nginx/access.log" "404"
实际工作中的应用场景
最后分享几个我在实际工作中经常遇到的场景:
场景1:网站被攻击时的应急响应
#!/bin/bash
# 应急响应脚本
echo "=== 安全事件分析 ==="
# 查找异常IP
echo "可疑IP (请求量 > 1000):"
awk '{print $1}' /var/log/nginx/access.log | \
sort | uniq -c | \
awk '$1 > 1000 {print $2, $1}' | \
sort -k2 -nr
# 查找SQL注入尝试
echo "SQL注入尝试:"
grep -i "union\|select\|drop\|insert" /var/log/nginx/access.log | \
awk '{print $1, $7}' | \
sort | uniq -c | \
sort -nr
# 查找文件包含攻击
echo "文件包含攻击:"
grep -E "\.\./|etc/passwd|proc/self" /var/log/nginx/access.log | \
awk '{print $1, $7}' | head -20
场景2:性能问题排查
#!/bin/bash
# 性能问题排查脚本
echo "=== 性能分析报告 ==="
# 慢请求分析 (假设nginx记录了响应时间)
echo "响应时间 > 2秒的请求:"
awk '$NF > 2.0 {print $1, $7, $NF"s"}' /var/log/nginx/access.log | \
sort -k3 -nr | head -20
# 大文件传输分析
echo "传输量 > 10MB的请求:"
awk '$10 > 10485760 {printf "%s %s %.2fMB\n", $1, $7, $10/1024/1024}' \
/var/log/nginx/access.log | sort -k3 -nr
# 并发分析
echo "每分钟请求数统计:"
awk '{print substr($4, 2, 16)}' /var/log/nginx/access.log | \
sort | uniq -c | \
awk '{print $2, $1}' | \
sort -k1
场景3:数据库连接问题排查
#!/bin/bash
# 数据库连接问题排查脚本
echo "=== 数据库连接分析 ==="
# 分析应用日志中的数据库错误
echo "数据库连接错误统计:"
grep -i "database\|mysql\|connection" /var/log/myapp/error.log | \
grep -E "(timeout|refused|failed|error)" | \
sed 's/.*\[\([0-9-]*\).*/\1/' | \
sort | uniq -c | \
awk '{printf "%s: %d次错误\n", $2, $1}'
# 分析慢查询日志
echo "慢查询TOP 10:"
if [ -f /var/log/mysql/slow.log ]; then
grep "Query_time" /var/log/mysql/slow.log | \
awk '{print $3}' | \
sort -nr | head -10 | \
awk '{printf "查询时间: %.2f秒\n", $1}'
fi
# 检查连接池状态
echo "当前数据库连接数:"
mysql -e "SHOW STATUS LIKE 'Threads_connected';" 2>/dev/null | \
awk 'NR==2 {print "活跃连接:", $2}'
场景4:磁盘空间清理
#!/bin/bash
# 磁盘空间分析和清理脚本
echo "=== 磁盘空间分析 ==="
# 找出占用空间最大的目录
echo "占用空间最大的目录 TOP 10:"
du -h /var 2>/dev/null | \
sort -hr | head -10 | \
awk '{printf "%-10s %s\n", $1, $2}'
# 找出大文件
echo "大于100MB的文件:"
find /var/log -type f -size +100M -exec ls -lh {} \; 2>/dev/null | \
awk '{printf "%-10s %s\n", $5, $9}' | \
sort -hr
# 分析日志文件增长趋势
echo "日志文件大小变化 (最近7天):"
for i in {0..6}; do
date_str=$(date -d "$i days ago" '+%Y-%m-%d')
total_size=$(find /var/log -name "*.log*" -newermt "$date_str 00:00:00" ! -newermt "$date_str 23:59:59" -exec du -cb {} + 2>/dev/null | tail -1 | awk '{print $1}')
if [ -n "$total_size" ] && [ "$total_size" -gt 0 ]; then
printf "%s: %.2f MB\n" "$date_str" $(echo "scale=2; $total_size/1024/1024" | bc)
fi
done
进阶技巧:处理复杂场景
多维度日志分析
有时候需要从多个维度分析日志,这个脚本我用了很多次:
#!/bin/bash
# 多维度nginx日志分析
LOG_FILE="/var/log/nginx/access.log"
multi_dimension_analysis() {
echo "=== 多维度分析报告 ==="
# 按小时统计访问量
echo "24小时访问量分布:"
awk '{
time = substr($4, 14, 2)
hour_count[time]++
} END {
for (h=0; h<24; h++) {
printf "%02d:00-%02d:59 %6d 次", h, h, hour_count[sprintf("%02d", h)]+0
# 简单的图形化显示
bars = int((hour_count[sprintf("%02d", h)]+0) / 100)
for (i=0; i<bars; i++) printf "█"
printf "\n"
}
}' "$LOG_FILE"
# IP地理位置分析 (需要geoip数据库)
echo "访问来源分析:"
awk '{print $1}' "$LOG_FILE" | \
sort | uniq -c | sort -nr | head -20 | \
while read count ip; do
# 这里可以集成GeoIP查询
printf "%-15s %8d 次\n" "$ip" "$count"
done
# 用户代理分析
echo "浏览器/爬虫统计:"
awk -F'"' '{
ua = $6
if (ua ~ /bot|spider|crawler/i) type = "爬虫"
else if (ua ~ /Mobile|Android|iPhone/i) type = "移动端"
else if (ua ~ /Chrome|Firefox|Safari/i) type = "桌面浏览器"
else type = "其他"
ua_type[type]++
} END {
for (type in ua_type) {
printf "%-12s %8d 次\n", type, ua_type[type]
}
}' "$LOG_FILE"
# 响应码时间分布
echo "响应码时间分布:"
awk '{
hour = substr($4, 14, 2)
status = $9
status_hour[status"_"hour]++
total_hour[hour]++
} END {
for (status in {"200":1, "404":1, "500":1}) {
printf "\n%s状态码分布:\n", status
for (h=0; h<24; h++) {
key = status"_"sprintf("%02d", h)
count = status_hour[key]+0
total = total_hour[sprintf("%02d", h)]+0
if (total > 0) {
percentage = count * 100 / total
printf "%02d时: %6d次 (%.1f%%)\n", h, count, percentage
}
}
}
}' "$LOG_FILE"
}
multi_dimension_analysis
实时日志监控告警
这个脚本可以实时监控日志并发送告警:
#!/bin/bash
# 实时日志监控告警脚本
LOG_FILE="/var/log/nginx/access.log"
ERROR_THRESHOLD=50 # 每分钟错误数阈值
ALERT_EMAIL="admin@example.com"
monitor_realtime() {
echo "开始实时监控 $LOG_FILE..."
# 创建临时文件记录状态
TEMP_DIR="/tmp/log_monitor"
mkdir -p "$TEMP_DIR"
tail -F "$LOG_FILE" | while read line; do
# 提取时间戳 (分钟级别)
timestamp=$(echo "$line" | awk '{print substr($4, 2, 16)}')
current_minute=$(date '+%d/%b/%Y:%H:%M')
# 检查是否是错误请求
if echo "$line" | grep -qE " (4[0-9]{2}|5[0-9]{2}) "; then
error_file="$TEMP_DIR/errors_$current_minute"
echo "$line" >> "$error_file"
# 统计当前分钟的错误数
error_count=$(wc -l < "$error_file" 2>/dev/null || echo 0)
if [ "$error_count" -ge "$ERROR_THRESHOLD" ]; then
send_alert "$current_minute" "$error_count" "$error_file"
# 重置计数器,避免重复告警
> "$error_file"
fi
fi
# 清理旧的临时文件
find "$TEMP_DIR" -name "errors_*" -mmin +5 -delete
done
}
send_alert() {
local time_period=$1
local error_count=$2
local error_file=$3
echo "🚨 告警: $time_period 发生 $error_count 个错误请求"
# 分析错误类型
echo "错误分析:"
awk '{print $9, $1, $7}' "$error_file" | \
sort | uniq -c | sort -nr | head -5 | \
awk '{printf "状态码%s: %d次, IP:%s, URL:%s\n", $2, $1, $3, $4}'
# 发送邮件告警 (需要配置sendmail)
if command -v mail >/dev/null; then
{
echo "时间: $time_period"
echo "错误数量: $error_count"
echo "详细信息:"
head -10 "$error_file"
} | mail -s "网站错误告警" "$ALERT_EMAIL"
fi
}
# 启动监控
monitor_realtime
性能优化的终极技巧
处理超大文件时,这些技巧能救命:
内存友好的大文件处理
#!/bin/bash
# 处理TB级别日志文件的技巧
process_huge_file() {
local file=$1
local chunk_size=${2:-1000000} # 默认100万行一个块
echo "处理大文件: $file ($(du -h "$file" | awk '{print $1}'))"
# 方法1: 分块处理
split -l "$chunk_size" "$file" "chunk_"
for chunk in chunk_*; do
echo "处理块: $chunk"
# 并行处理每个块
{
awk '{
# 你的处理逻辑
ip_count[$1]++
} END {
for (ip in ip_count) {
print ip, ip_count[ip] > "result_'$chunk'.txt"
}
}' "$chunk"
rm "$chunk" # 处理完立即删除
} &
# 控制并发数
(($(jobs -r | wc -l) >= 4)) && wait
done
wait # 等待所有后台任务完成
# 合并结果
echo "合并结果..."
awk '{sum[$1] += $2} END {
for (ip in sum) print ip, sum[ip]
}' result_chunk_*.txt | sort -k2 -nr > final_result.txt
rm result_chunk_*.txt
}
# 方法2: 流式处理 (内存占用最小)
stream_process() {
local file=$1
# 使用管道流式处理,内存占用恒定
cat "$file" | \
awk '{
# 每处理10万行输出一次中间结果
if (NR % 100000 == 0) {
print "处理进度:", NR > "/dev/stderr"
}
# 你的处理逻辑
ip_count[$1]++
# 定期清理内存 (保留热点数据)
if (NR % 1000000 == 0) {
for (ip in ip_count) {
if (ip_count[ip] < 10) delete ip_count[ip]
}
}
} END {
for (ip in ip_count) {
print ip, ip_count[ip]
}
}' | sort -k2 -nr
}
分布式处理
当单机处理不过来时,可以用这种方式分布到多台机器:
#!/bin/bash
# 分布式日志处理脚本
SERVERS=("server1" "server2" "server3")
LOG_FILE="/var/log/nginx/access.log"
distribute_process() {
local total_lines=$(wc -l < "$LOG_FILE")
local lines_per_server=$((total_lines / ${#SERVERS[@]}))
echo "总行数: $total_lines, 每台服务器处理: $lines_per_server 行"
for i in "${!SERVERS[@]}"; do
local server="${SERVERS[$i]}"
local start_line=$((i * lines_per_server + 1))
local end_line=$(((i + 1) * lines_per_server))
echo "分发给 $server: 行 $start_line - $end_line"
# 提取对应行数并发送到远程服务器处理
sed -n "${start_line},${end_line}p" "$LOG_FILE" | \
ssh "$server" "
awk '{ip_count[\$1]++} END {
for (ip in ip_count) print ip, ip_count[ip]
}' > /tmp/result_$i.txt
" &
done
wait
# 收集结果
echo "收集结果..."
for i in "${!SERVERS[@]}"; do
scp "${SERVERS[$i]}:/tmp/result_$i.txt" "result_$i.txt"
done
# 合并最终结果
awk '{sum[$1] += $2} END {
for (ip in sum) print ip, sum[ip]
}' result_*.txt | sort -k2 -nr > distributed_result.txt
rm result_*.txt
}
END~
经过这么多年的使用,我总结出几个关键点:
学习建议:
- 从简单的grep开始,逐步掌握正则表达式
- sed重点掌握替换和删除操作,高级功能按需学习
- awk是重点,要理解它的编程思维
- 多练习组合使用,这是真正提升效率的关键
性能优化原则:
- 能用grep解决的不用awk
- 处理大文件时优先考虑内存使用
- 善用管道,减少临时文件
- 并行处理要控制好并发数
实战经验:
- 先用小数据集测试命令
- 复杂逻辑写成脚本,方便复用
- 重要操作前先备份数据
- 多写注释,方便后续维护
这三个工具真的是运维工作中不可或缺的利器,掌握了它们,处理各种文本和日志分析任务都会变得轻松很多。我现在每天都在用,从简单的日志查看到复杂的数据分析,基本上都能搞定。
如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!
公众号:运维躬行录
个人博客:躬行笔记