Shell脚本性能优化:如何加速每日处理15万文件的批量执行脚本
Shell脚本优化方案
原脚本核心问题
- 语法错误:
sed命令行后多了一行无效的目标文件路径,执行会直接报错 - 遍历文件/行用了
$(ls)、$(cat)写法,不仅存在文件名/行内容包含特殊字符的兼容性问题,还会额外创建子进程 - 每行处理都启动
awk、cut、grep、sed四个独立子进程,百万级的进程调度开销是最大的性能瓶颈 - 频繁执行小IO操作:逐行grep扫描目标文件、
sed -i重写整个目标文件,IO开销极高
优化方案
1. Shell原生语法优化(改动最小,性能提升3~5倍)
把所有外部命令替换成Shell内置操作,减少子进程创建,优化IO逻辑:
#!/bin/bash shopt -s nullglob # 匹配不到文件时glob返回空,避免报错 DATA_FILE_SOURCE="<path_to_source_data/${1}" DATA_FILE_DEST="<path_to_dest>" # 提前创建目标目录避免报错 mkdir -p "${DATA_FILE_DEST}" || exit 1 # 用glob遍历文件,不要解析ls for fname in "${DATA_FILE_SOURCE}"/*; do # 跳过非文件项 [[ -f "${fname}" ]] || continue # 用内置read逐行读文件,不要用cat while IFS=, read -r col1 col2 content || [[ -n "${content}" ]]; do target_file="${DATA_FILE_DEST}/${col1}.${col2}.daily.csv" if [[ ! -f "${target_file}" ]]; then echo "${content}" > "${target_file}" else if ! grep -Fxq "${content}" "${target_file}"; then # 保留原脚本删除含${1}行的逻辑,确认符合业务需求再使用 sed -i "/${1}/d" "${target_file}" echo "${content}" >> "${target_file}" fi fi done < "${fname}" done
2. 全量awk实现(性能提升10~20倍,推荐)
awk是文本处理专用工具,单进程即可完成所有分割、去重、写文件操作,完全避免频繁子进程和碎片化IO:
#!/usr/bin/awk -f BEGIN { # 传入参数说明:ARGV[1]为脚本第一个参数$1,ARGV[2]为目标路径 src_prefix = ARGV[1] dest_path = ARGV[2] ARGV[1] = "" ARGV[2] = "" FS = "," } # 逐行处理所有输入文件 { target_file = dest_path "/" $1 "." $2 ".daily.csv" content = substr($0, length($1) + length($2) + 3) # 内存去重:同个目标文件的重复内容自动覆盖 file_content[target_file, content] = 1 } END { # 所有行处理完成后,一次性批量写入所有目标文件 for (key in file_content) { split(key, arr, SUBSEP) f = arr[1] c = arr[2] # 首次写该文件时先处理原有内容 if (!(f in written)) { written[f] = 1 # 原文件存在的话先过滤掉含src_prefix的行 if (system("test -f " f) == 0) { while ((getline line < f) > 0) { if (line !~ src_prefix) { print line > f } } close(f) } } # 写入去重后的新内容 print c >> f close(f) } }
调用方式:
chmod +x process.awk ./process.awk "${1}" "<path_to_dest>" <path_to_source_data/${1}>/*
额外优化建议
- 如果目标文件不需要保留历史内容,直接覆盖写入即可,省去读旧文件的开销
- 目标文件内容较多的场景,可以用
sort -u批量去重,比逐行grep效率高很多 - 存在磁盘IO瓶颈的话,可以把目标目录放到tmpfs内存文件系统处理,完成后再同步到磁盘
内容的提问来源于stack exchange,提问作者usert4jju7
相关产品推荐
相关产品推荐

