如何用Bash快速对大文件执行大量递归单词替换?
问题描述
现有Bash脚本用于实现递归变量替换:读取属性文件中的键值对(支持$、&等特殊字符),将目标文本中的变量(以$开头、单词边界\b结尾)替换为对应值,支持递归替换(如$foo替换为包含$barname的内容,再进一步替换$barname)。
但当前实现性能极差:属性文件和目标文本均为数百行规模时,系统负载高的情况下处理时长可达数分钟甚至超一小时。现有核心脚本如下:
INPUT=`cat $INPUT_FILE` while read line; do PROP_NAME="$(echo $line | cut -f1 -d'=')" PROP_VALUE="$(echo $line | cut -f2- -d'=' | sed 's/\$/\\\$/g' | sed 's/\&/\\\&/g')" INPUT="$(echo "$INPUT" | sed "s\`${PROP_NAME}\b\`${PROP_VALUE}\`g")" done < "$PROPERTIES_FILE" echo "$INPUT"
示例数据
- 属性文件:
$foo=$barname bar $barname=Tom&Jerry $hello=world
- 目标文本输入:
I went to the $foo and said hello to the $hello and they fined me $5.
- 预期输出:
I went to the Tom&Jerry bar and said hello to the world and they fined me $5.
关键约束
- 变量匹配需遵循单词边界:仅替换以标点/空白/行尾结尾的变量引用(如
$5不会被误替换) - 递归替换仅遍历属性文件一次,无无限递归,但属性顺序会影响最终结果
- 需正确保留
$、&等特殊字符
高效优化方案
原脚本的核心性能瓶颈是:循环中反复调用sed/cut生成子进程,且每次都要重新扫描全量目标文本,导致IO和进程调度开销爆炸。以下是基于哈希映射的高效实现:
方案1:用awk实现递归替换(推荐)
awk可一次性加载所有属性到哈希表,然后单次遍历目标文本完成递归替换,全程仅启动一个进程,性能提升显著。
实现代码
awk -F'=' ' # 第一阶段:加载属性文件到哈希表,保留属性顺序 NR == FNR { key = $1 value = substr($0, index($0, "=")+1) # 用数组保存属性顺序,保证替换顺序和原脚本一致 keys[++key_cnt] = key map[key] = value next } # 第二阶段:处理目标文本,递归替换变量 { line = $0 # 循环替换直到无变量可替换 while (1) { changed = 0 # 按属性文件的顺序替换,保证和原脚本行为一致 for (i=1; i<=key_cnt; i++) { key = keys[i] # 匹配单词边界:\< 表示词首,\> 表示词尾 regex = "\\<" key "\\>" if (gsub(regex, map[key], line)) { changed = 1 } } if (!changed) break } print line } ' "$PROPERTIES_FILE" "$INPUT_FILE"
关键点说明
- 哈希表+顺序数组:既用哈希表快速查找变量,又用数组保存属性顺序,保证替换逻辑和原脚本完全一致
- 递归替换:循环扫描文本直到没有可替换的变量,实现递归替换效果
- 单词边界匹配:用awk的
\</\>实现标准单词边界,等价于原脚本的\b - 低开销:全程仅一个awk进程,避免了原脚本中数百次的子进程调用和全量文本重复扫描
方案2:预生成sed脚本(适合非递归/可控顺序场景)
如果递归逻辑可通过调整属性顺序实现(比如先定义深层变量$barname,再定义依赖它的$foo),可以预生成完整的sed脚本,单次执行替换:
实现代码
# 生成sed替换规则,转义特殊字符 sed_script=$(awk -F'=' ' { key = $1 value = substr($0, index($0, "=")+1) # 转义sed特殊字符:\、$、& gsub(/\\/, "\\\\", value) gsub(/\$/, "\\$", value) gsub(/&/, "\\&", value) print "s/" key "\\b/" value "/g" } ' "$PROPERTIES_FILE") # 一次性执行替换 sed -e "$sed_script" "$INPUT_FILE"
说明
- 预先生成所有替换规则,单次调用sed处理目标文本,避免循环调用sed的开销
- 局限性:仅支持单次替换,若需递归替换必须调整属性文件顺序,否则无法完成深层替换
性能对比
- 原脚本:处理数百行文件时,每次循环生成子进程并扫描全量文本,负载高时IO和进程调度开销剧增
- 优化方案:仅启动1-2个进程,单次扫描文本,性能提升可达数十倍甚至上百倍,系统负载变化对处理时长影响极小
内容的提问来源于stack exchange,提问作者Bryan Tan
相关产品推荐
相关产品推荐

