多文件(任意数量)按不同字段关联,缺失字段填充默认值问题
多文件通用关联问题及脚本修复
问题背景
该场景比现有多文件关联逻辑更通用,实现时遇到输出格式异常问题:部分默认值0缺失,导致字段错位。
需求说明
输入文件数量可变,需根据指定的关联字段将所有文件按KEY聚合,非关联字段若无对应值则填充0,输出以Tab分隔。
输入文件示例
$ cat f1.tsv F11 F12 F13 111 A 113 211 B 213 $ cat f2.tsv F21 F22 121 D $ cat f3.tsv F31 F32 F33 F24 131 132 133 C 231 232 233 A 331 332 333 D $ cat fn.tsv Fn1 Fn2 Fn3 B 102 103 F 202 203 D 302 303 C 402 403
关联参数
传入文件列表:f1.tsv f2.tsv f3.tsv fn.tsv
关联字段索引(从1开始):2 2 4 1
预期输出
F11 F13 F21 F31 F32 F33 Fn2 Fn3 KEY 111 113 0 231 232 233 0 0 A 211 213 0 0 0 0 102 103 B 0 0 0 131 132 133 402 403 C 0 0 121 331 332 333 302 303 D 0 0 0 0 0 0 202 203 F
注:输入输出字段均以单个Tab字符分隔
现有脚本问题
编写的POSIX awk脚本如下,但实际输出存在字段错位,缺失部分默认值填充:
现有脚本
awk -v join='2 2 4 1' -v empty='0' ' BEGIN { FS = OFS = "\t" if ( err = (ARGC <= 2 || split(join,joinArr,"[[:space:],]") != ARGC-1) ) exit } FNR == 1 { ++fileNumber j = joinArr[fileNumber]+0 if ( err = (j < 1 || NF < j) ) exit rec = sep = "" for (i = 1; i <= NF; i++) if (i != j) { rec = rec sep $i sep = OFS } header = (fileNumber == 1 ? rec : header OFS rec) next } { rec = sep = "" for (i = 1; i <= NF; i++) if (i != j) { rec = rec sep $i sep = OFS } keys[$j] records[fileNumber,$j] = rec } END { if (err) exit err print header, "KEY" for (k in keys) { rec = sep = "" for (i = 1; i <= fileNumber; i++) { rec = rec sep ((i,k) in records ? records[i,k] : empty) sep = OFS } rec = rec sep k print rec } } ' f1.tsv f2.tsv f3.tsv fn.tsv
实际异常输出
F11 F13 F21 F31 F32 F33 Fn2 Fn3 KEY 111 113 0 231 232 233 0 A 211 213 0 0 102 103 B 0 0 131 132 133 402 403 C 0 121 331 332 333 302 303 D 0 0 0 202 203 F
问题根源与修复
问题根源
脚本中默认填充单个empty值,但每个文件去掉关联字段后的字段数量不同:
- f1.tsv:去掉第2字段后剩2个字段,需填充
0\t0 - f2.tsv:去掉第2字段后剩1个字段,需填充
0 - f3.tsv:去掉第4字段后剩3个字段,需填充
0\t0\t0 - fn.tsv:去掉第1字段后剩2个字段,需填充
0\t0
原脚本统一填充单个0,导致多字段文件的默认值缺失,引发字段错位。
修复后的脚本
在处理每个文件首行时,生成对应字段数的空值模板,存储到数组中,后续缺失记录时使用对应模板填充:
awk -v join='2 2 4 1' -v empty='0' ' BEGIN { FS = OFS = "\t" if ( err = (ARGC <= 2 || split(join,joinArr,"[[:space:],]") != ARGC-1) ) exit } FNR == 1 { ++fileNumber j = joinArr[fileNumber]+0 if ( err = (j < 1 || NF < j) ) exit # 生成当前文件的非关联字段列表和空值模板 rec = sep = "" empty_rec = "" field_cnt = 0 for (i = 1; i <= NF; i++) { if (i != j) { rec = rec sep $i empty_rec = (field_cnt++ ? empty_rec OFS : "") empty sep = OFS } } header = (fileNumber == 1 ? rec : header OFS rec) emptyArr[fileNumber] = empty_rec # 存储当前文件的空值模板 next } { rec = sep = "" for (i = 1; i <= NF; i++) if (i != j) { rec = rec sep $i sep = OFS } keys[$j] records[fileNumber,$j] = rec } END { if (err) exit err print header, "KEY" for (k in keys) { rec = sep = "" for (i = 1; i <= fileNumber; i++) { # 使用对应文件的空值模板填充 rec = rec sep ((i,k) in records ? records[i,k] : emptyArr[i]) sep = OFS } rec = rec sep k print rec } } ' f1.tsv f2.tsv f3.tsv fn.tsv
兼容性说明
该脚本符合POSIX标准,兼容Linux和macOS环境;也可使用以下替代方案:
- Perl方案:利用哈希表聚合KEY,预存各文件的字段结构与空值模板,逻辑与awk一致
- Python方案:兼容Python2.7/3.x,使用字典存储KEY对应的各文件数据,pandas库可简化实现(需确保环境安装pandas)
内容的提问来源于stack exchange,提问作者Fravadona
相关产品推荐
相关产品推荐

