Bash下用while read与sed为管道分隔文件指定列加前缀的异常问题
原有实现的问题根因
你的实现思路从逻辑设计上就存在缺陷,并非简单的命令使用问题,具体错误点:
sed -i是直接对磁盘上的原文件做全局文本替换,而你的循环是逐行读取内存中暂存的当前行内容做字段提取,二者没有逻辑绑定:
每处理一行INVOICES记录时,sed会把全文件所有和当前债务人账号字符串匹配的位置全部加上C前缀,既包括其他行的同账号INVOICES记录,也包括同账号的C/NOTES记录;循环走到下一条同账号记录时,提取到的账号已经带上了之前加的C,sed会再次做替换,最终就出现了连续叠N个C、C/NOTES行被错误加C的问题- 原始文件中
C/NOTES字段尾部自带空格,直接用字符串等值判断永远匹配不上该类型,进一步加剧了类型判断失效的问题 - 全局字符串匹配没有锚定字段位置,只要其他列出现和债务人账号相同的文本,就会被误加前缀,存在数据写错的风险
最优解决方案
针对竖线分隔的定界文本,完全不需要嵌套shell循环、逐行调用awk和sed子进程,直接用单进程awk完成全流程处理即可,从机制上规避反复改文件的问题,同时处理效率更高:
# 处理逻辑:逐行读取文件,按|拆分字段,匹配规则后修改第4列内容,最后输出到新文件 awk -F'|' ' # 定义去空格函数,解决原文件字段带前后空格的匹配问题 function trim(str) { gsub(/^[[:space:]]+|[[:space:]]+$/,"",str) return str } { # 去除字段首尾空格后做规则匹配 field2 = trim($2) field3 = trim($3) field4 = $4 if (field2 == "0202" && field3 == "INVOICES") { $4 = "C" field4 } else if (field2 == "0202" && field3 == "C/NOTES") { $4 = "R" field4 } # 保持原竖线分隔格式重组行内容输出 OFS = "|" print }' 原始文件 > 处理后文件
方案优势
- 全程逐行在内存中处理,不会反复读写修改原文件,从根源上避免同一内容被多次叠加前缀的问题
- 基于固定字段位置做判断和修改,不会跨列匹配误伤其他内容
- 自动去除字段首尾空格,适配原文件中字段尾部带空格的格式
- 处理结果先输出到独立新文件,校验无误后再替换原文件即可,不会破坏原始数据
如果一定要沿用原来的shell循环写法,需要改成“逐行处理后追加写入临时文件,循环完成后用临时文件替换原文件”的模式,但这种写法会启动大量子进程,处理大文件时性能远差于awk单进程方案,不推荐使用。
内容的提问来源于stack exchange,提问作者NovaOrsa
相关产品推荐
相关产品推荐

