Shell脚本如何分割大日志文件

wen 实用脚本 1

Shell脚本高效分割大日志文件:实战技巧与最佳实践

📖 目录导读

  1. 为什么需要分割大日志文件?
  2. 核心工具:split命令详解
  3. 基于行数的分割方案
  4. 基于文件大小的分割方案
  5. 按时间窗口分割的进阶技巧
  6. 实战脚本:自动化分割+压缩+清理
  7. 常见问题FAQ
  8. 性能优化与注意事项

为什么需要分割大日志文件?

Q:日志文件多大算“大”?为什么必须分割?
A:当单个日志文件超过100MB时,viless等文本工具会变得卡顿;超过1GB时,grepawk命令可能耗尽内存;线上生产环境常见日志文件可达10GB+,分割的核心原因包括:

Shell脚本如何分割大日志文件

  • 排查效率:小文件可秒级打开,大文件需分钟级加载
  • 避免内存溢出tail -fgrep可能消耗大量内存
  • 便于归档备份:单文件太大无法直接上传至云存储
  • 满足工具限制:部分日志分析工具(如Logstash)对单文件大小有硬限制

核心工具:split命令详解

Linux的split命令是分割文本文件的瑞士军刀,基础用法:

# 按行数分割(每10000行一个文件)
split -l 10000 big.log chunk_
# 按文件大小分割(每50MB一个文件)
split -b 50M big.log chunk_
# 保留原文件扩展名(.log)
split -d -l 10000 big.log log_part_ --additional-suffix=.log

关键参数说明

  • -l:行数分割(最常用)
  • -b:大小分割(支持K/M/G)
  • -d:数字后缀(默认字母后缀如aaab
  • --additional-suffix:添加扩展名(需新版coreutils)

基于行数的分割方案

Q:如何确保每段日志不会“断行”?
A:行数分割天然按换行符切割,不会截断单行日志,推荐脚本:

#!/bin/bash
# split_by_lines.sh
log_file="/var/log/nginx/access.log"
lines_per_file=50000
output_prefix="access_part_"
# 创建输出目录
output_dir="/tmp/split_log/$(date +%Y%m%d)"
mkdir -p "$output_dir"
# 执行分割
split -l $lines_per_file -d --additional-suffix=.log \
  "$log_file" "$output_dir/$output_prefix"
echo "分割完成,共生成 $(ls $output_dir/*.log | wc -l) 个文件"

适用场景:Nginx、Apache访问日志,每行代表一次请求,适合按行分析。


基于文件大小的分割方案

Q:行数不固定时如何按大小精准分割?
A:用-b参数按字节切割,但注意可能切在行中间,解决方案:

#!/bin/bash
# split_by_size.sh
raw_file="app.log"
target_size=$((50 * 1024 * 1024)) # 50MB
# 方案1:直接按大小切(可能断行)
split -b $target_size -d "$raw_file" "large_part_" --additional-suffix=.log
# 方案2:先用dd模拟切割再修正(开销大)
# 实际推荐先用wc -l估算行数,再用行数分割
total_lines=$(wc -l < "$raw_file")
bytes_per_line=$(($(stat -c%s "$raw_file") / total_lines))
lines_per_file=$((target_size / bytes_per_line))
split -l $lines_per_file -d "$raw_file" "chunk_" --additional-suffix=.log

注意事项

  • 大小分割更适合二进制文件(如压缩包)
  • 日志文件推荐始终用行数分割,避免解析错误

按时间窗口分割的进阶技巧

Q:如何按小时/天分割持续写入的日志?
A:利用Linux cron + logrotate 或自定义脚本:

#!/bin/bash
# split_by_hour.sh - 监控日志文件并按小时分割
log_path="/data/logs/service.log"
output_path="/data/logs/archive"
last_check=$(date -d "1 hour ago" +%Y%m%d%H)
while true; do
    current_hour=$(date +%Y%m%d%H)
    if [ "$current_hour" != "$last_check" ]; then
        # 复制当前文件到归档
        cp "$log_path" "$output_path/service_${last_check}.log"
        # 清空原日志(不中断写入进程)
        > "$log_path"
        last_check=$current_hour
    fi
    sleep 60
done

生产环境推荐工具

  • logrotate:官方日志轮转工具,支持压缩、删除、邮件通知
  • cronolog:Apache通用日志轮转,可按日期自动分割

配置示例(/etc/logrotate.d/mylog):

/data/logs/*.log {
    daily
    rotate 7
    compress
    missingok
    copytruncate
    dateext
    postrotate
        systemctl reload myapp
    endscript
}

实战脚本:自动化分割+压缩+清理

综合以上方案,编写一个全功能脚本:

#!/bin/bash
# advanced_log_splitter.sh
# 功能:按行分割->压缩->清理旧文件->记录日志
SETTINGS_FILE="/etc/split_log.conf"
[ -f "$SETTINGS_FILE" ] && source "$SETTINGS_FILE"
# 默认配置
: "${SOURCE_DIR:=/var/log/myapp}"
: "${ARCHIVE_DIR:=/backup/logs}"
: "${LINES_PER_FILE:=100000}"
: "${RETENTION_DAYS:=90}"
: "${COMPRESSOR:=gzip}"
: "${LOG_FILE:=/var/log/split_log.log}"
log_msg() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
split_logs() {
    local src_file="$1"
    local basename=$(basename "$src_file" .log)
    local today=$(date +%Y%m%d)
    local out_dir="$ARCHIVE_DIR/$today"
    mkdir -p "$out_dir"
    # 分割并压缩
    split -l $LINES_PER_FILE -d --additional-suffix=.tmp \
        "$src_file" "$out_dir/${basename}_part_"
    for tmp_file in "$out_dir"/*.tmp; do
        # 添加时间戳后缀
        local final_name="${tmp_file%.tmp}_$today.log"
        mv "$tmp_file" "$final_name"
        # 压缩(异步执行)
        ($COMPRESSOR "$final_name" && log_msg "压缩完成: $final_name") &
    done
    # 截断原日志(确保不丢新日志)
    : > "$src_file"
    log_msg "已分割: $src_file"
}
# 清理旧目录
cleanup_old() {
    find "$ARCHIVE_DIR" -type d -name "2*" -mtime +$RETENTION_DAYS -exec rm -rf {} \;
    log_msg "清理超过${RETENTION_DAYS}天的归档目录"
}
# 主逻辑
for log_file in "$SOURCE_DIR"/*.log; do
    [ -f "$log_file" ] && split_logs "$log_file"
done
cleanup_old

使用方式

  1. 保存为/usr/local/bin/split_logs
  2. 添加执行权限:chmod +x /usr/local/bin/split_logs
  3. 配置定时任务:crontab -e 添加 0 3 * * * /usr/local/bin/split_logs

常见问题FAQ

Q1:分割后日志中出现了乱码或空行?
A:这是因为原日志文件中可能包含非UTF-8字符(如二进制数据),解决方案:

  • 使用iconv -f gbk -t utf-8转码后再分割
  • 添加-a 4参数增加后缀位数避免覆盖

Q2:分割时正在写入的日志文件如何处理?
A:推荐使用:

  • copytruncate(logrotate模式):复制文件并截断原文件
  • 或先cp>>清空(需确保应用支持信号重开日志)

Q3:分割后文件名太长怎么办?
A:使用-a 3指定数字位数为3(如part_001.log),或改用日期命名模式:

split -l 10000 -d --additional-suffix=".log" input.log "$(date +%Y%m%d_%H%M%S)_"

Q4:如何验证分割结果完整性?
A:计算原文件总行数与分割后各文件行数和是否一致:

original_lines=$(wc -l < raw.log)
total_split_lines=$(cat chunk_*.log | wc -l)
[ "$original_lines" -eq "$total_split_lines" ] && echo "完整" || echo "缺失"

性能优化与注意事项

✅ 最佳实践

  • 优先使用行数分割:对文本日志99%场景适用
  • 设置适当的分割大小:建议10000-50000行或50-200MB
  • 利用管道流式处理:避免生成中间临时文件
    zcat biglog.gz | split -l 10000 -d - chunk_
  • 启用压缩:分割后立即gzip可节省80%存储空间

⚠️ 避坑指南

  • 不要分割/var/log/wtmp:二进制日志需用专用工具
  • 避免在高负载时分割:建议低峰期(凌晨)执行
  • 保留源文件权限:分割后需重新设置权限chmod 640 *
  • 监控磁盘I/O:超大文件分割可能触发I/O瓶颈,建议限速:
    ionice -c 2 -n 7 split -l 10000 big.log # 使用低优先级

🔧 性能对比表

方法 适用场景 速度 内存消耗 推荐度
split -l 纯文本日志 首选
split -b 二进制文件 次选
awk 逐行处理 复杂分割规则 特定场景
Python脚本 需要正则过滤 极高 不推荐用于大文件

最后提醒:生产环境操作前请先测试!建议在测试服务器用dd if=/dev/zero of=test.log bs=1M count=200生成200MB测试文件,验证脚本逻辑后再部署到线上。

抱歉,评论功能暂时关闭!