如何清洗字段内含分隔符的TSV文件 替换引号内制表符/换行符为空格
问题描述
现有TSV文件的col2列被双引号包裹的内容中,包含字段分隔符(制表符Tab)或记录分隔符(回车换行符),原始文件示例如下:
$ printf '%b\n' 'col1\tcol2\tcol3' '1\t"A\tB"\t1234' '2\t"CD\nEF"\t567' | \cat -vet col1^Icol2^Icol3$ 1^I"A^IB"^I1234$ 2^I"CD$ EF"^I567$
直接按普通TSV解析会出现列数错位,错误解析效果:
+------+---------+------+ | col1 | col2 | col3 | +------+---------+------+ | 1 | "A B" | 1234 | | 2 | "CD | 567 | | | EF" | | +------+---------+------+
需求为:将引号包裹区域内的制表符、换行符替换为空格,最终得到正确解析结果:
+------+---------+------+ | col1 | col2 | col3 | +------+---------+------+ | 1 | "A B" | 1234 | | 2 | "CD EF" | 567 | +------+---------+------+
使用miller 6.2版本默认TSV模式处理时,工具无法识别引号内的制表符/CR/LF属于字段内容,会触发长度不匹配报错:
$ printf '%b\n' 'col1\tcol2\tcol3' '1\t"A\tB"\t1234' '2\t"CD\nEF"\t567' | mlr --opprint --barred --itsv cat mlr: CSV header/data length mismatch 3 != 4 at filename (stdin) line 2.
可行解决方案
方案1:Perl 单行预处理(兼容性最好,逻辑最严谨)
通过状态机跟踪双引号开闭状态,仅替换引号内部的制表符、回车、换行符为空格,完全不破坏引号外的TSV结构,处理后的标准TSV可直接被任意工具解析。
perl -0777 -pe ' my $in_quote = 0; s/(")|[\t\r\n]/$1 ? ($in_quote=!$in_quote, $1) : ($in_quote ? " " : $&)/ge; ' 输入文件.tsv > 清理后标准.tsv
配合miller直接输出目标格式的命令:
printf '%b\n' 'col1\tcol2\tcol3' '1\t"A\tB"\t1234' '2\t"CD\nEF"\t567' | \ perl -0777 -pe 'my $in_quote = 0; s/(")|[\t\r\n]/$1 ? ($in_quote=!$in_quote, $1) : ($in_quote ? " " : $&)/ge;' | \ mlr --itsv --opprint --barred cat
方案2:直接使用Miller(无需额外工具)
Miller默认的--itsv严格TSV模式不支持引号转义规则,但--csv模式遵循RFC4180标准,原生支持双引号包裹字段内的分隔符、换行符,只需手动指定字段分隔符为制表符即可正确解析,再替换字段内的特殊字符即可。
printf '%b\n' 'col1\tcol2\tcol3' '1\t"A\tB"\t1234' '2\t"CD\nEF"\t567' | \ mlr --csv --fs tab --allow-cr-in-quoted-fields put 'for (k in $*) {gsub($[k], /[\t\r\n]/, " ")}' | \ mlr --itsv --opprint --barred cat
方案3:Awk 状态机实现(无perl/mlr环境时使用)
通过Awk实现引号状态跟踪,逐字符处理内容,自动拼接引号内的跨行内容,替换内部特殊字符为空格:
awk ' { cur_line = $0 output = "" for (i = 1; i <= length(cur_line); i++) { c = substr(cur_line, i, 1) if (c == "\"") { in_quote = !in_quote output = output c } else if (in_quote && (c == "\t" || c == "\r")) { output = output " " } else { output = output c } } if (in_quote) { printf "%s ", output } else { print output } } ' 输入文件.tsv
以上三种方案处理示例输入,均可得到预期的正确解析结果。
内容的提问来源于stack exchange,提问作者Cyril Chaboisseau
相关产品推荐
相关产品推荐

