想象下,当你遇到几十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

这个命令组合:

  1. grep筛选今天的日志
  2. grep找出4xx和5xx错误
  3. awk提取IP地址
  4. sort和uniq统计每个IP的错误次数
  5. awk筛选错误超过100次的IP
  6. 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~

经过这么多年的使用,我总结出几个关键点:

学习建议:

  1. 从简单的grep开始,逐步掌握正则表达式
  2. sed重点掌握替换和删除操作,高级功能按需学习
  3. awk是重点,要理解它的编程思维
  4. 多练习组合使用,这是真正提升效率的关键

性能优化原则:

  1. 能用grep解决的不用awk
  2. 处理大文件时优先考虑内存使用
  3. 善用管道,减少临时文件
  4. 并行处理要控制好并发数

实战经验:

  1. 先用小数据集测试命令
  2. 复杂逻辑写成脚本,方便复用
  3. 重要操作前先备份数据
  4. 多写注释,方便后续维护

这三个工具真的是运维工作中不可或缺的利器,掌握了它们,处理各种文本和日志分析任务都会变得轻松很多。我现在每天都在用,从简单的日志查看到复杂的数据分析,基本上都能搞定。

如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!

公众号:运维躬行录

个人博客:躬行笔记

标签: none