我想根据公共列合并多个文件,并希望在任何文件没有该公共列时添加 0。例如见下图:
a1.txt
111,222,444,5.5
121,321,555,1.2
a2.txt
111,222,444,7.8
333,321,555,4.5
311,555,222,1.1
a3.txt
333,321,555,9.1
311,555,222,8.8
444,666,777,2.5
匹配应与前 3 列的组合。
输出应如下所示:
111,222,444,5.5,7.8,0
121,321,555,1.2,0,0
333,321,555,0,4.5,9.1
311,555,222,0,1.1,8,8
444,666,777,0,0,2.5
3 个输入文件中第 4 列的值不同,我想按顺序放置。就像 a1.txt 值应该是输出文件中的第四列。 a2.txt 值应位于输出文件的第 5 列,a3.txt 的值应位于输出文件的第 6 列。我在下面尝试过,但没有给我预期的结果。
awk '{ a[$1 FS $2 FS $3 FS] = a[$1 FS $2 FS $3 FS] ( a[$1 FS $2 FS $3 FS] == "" ? "" : FS) $4 } END{ for (i in a){print i,a[i,0],a[i]} }' FS="," a1.txt a2.txt a3.txt
这样我想对 4 或 5 或 6 个输入文件执行相同的操作。有人可以帮我解决这个问题吗?
答案1
使用任何 awk 并保持输出中记录的顺序:
awk 'BEGIN{ SUBSEP=OFS=FS="," }
FNR==1 && !reProccss{ fileNr++ }
!reProccss{ keys[$1, $2, $3, fileNr]=$4; next }
reProccss{ key=($1 OFS $2 OFS $3); recNr++
for(i=1; i<=fileNr; i++)
if(seen[key]++<fileNr){
join[key]= join[key] OFS ((key, i) in keys ?keys[key, i]:"0")
data[recNr]= key join[key]
}
}
END{ for(rec=1; rec<=recNr; rec++)
if(data[rec]!="")
print data[rec]
}' a[1-3].txt reProccss=1 a[1-3].txt
或者使用join
+shell 将多个列作为键转换为单个键,然后我们使用join
答案类似于的命令按第一列合并多个文件(因为 join 仅适用于单列作为键)以产生我们所需的输出。
-
因此,我们通过在前两个文件上用特定字符(例如输入文件中不应该存在的字符)分隔多个键列,将它们转换为一个键列,然后输出到临时文件joined.tmp
:
join -t, -a1 -a2 -e 0 -o auto \
<(<a1.txt sort |awk -F, -v OFS='-' '{ print $1, $2, $3 FS $4 }') \
<(<a2.txt sort |awk -F, -v OFS='-' '{ print $1, $2, $3 FS $4 }') > joined.tmp
然后我们使用 shell 循环来处理其余文件joined.tmp
文件(每次运行都会更新该文件以与下一个文件连接);我们还跳过我们已经在循环中处理过的前两个文件。
for file in ./a*.txt; do
[ "$file" = "./a1.txt" -o "$file" = "./a2.txt" ] && continue
join -t, -a1 -a2 -e 0 -o auto \
joined.tmp <(sort "$file" |awk -F, -v OFS='-' '{ print $1, $2, $3 FS $4 }') >joined.tmp.1
mv joined.tmp.1 joined.tmp
done
最后将添加的字符改回-
原来的字符,
。
sed 's/-/,/g' joined.tmp > joined-final.csv
输出中的记录顺序将发生变化,因为join
需要对输入文件进行排序:
$ cat joined-final.csv
111,222,444,5.5,7.8,0
121,321,555,1.2,0,0
311,555,222,0,1.1,8.8
333,321,555,0,4.5,9.1
444,666,777,0,0,2.5
答案2
对数组数组和 ARGIND 使用 GNU awk:
$ cat tst.awk
BEGIN { FS=OFS=SUBSEP="," }
{ vals[$1,$2,$3][ARGIND] = $NF }
END {
for ( key in vals ) {
printf "%s", key
for ( i=1; i<=ARGIND; i++ ) {
printf "%s%g", OFS, vals[key][i]
}
print ""
}
}
$ awk -f tst.awk *.txt
111,222,444,5.5,7.8,0
311,555,222,0,1.1,8.8
333,321,555,0,4.5,9.1
444,666,777,0,0,2.5
121,321,555,1.2,0,0
如果输出线的顺序很重要,那么调整很容易。