通过 awk 合并具有 N 个公共列的多个文件,如果任何文件没有公共键,则希望将列值替换为 0

通过 awk 合并具有 N 个公共列的多个文件,如果任何文件没有公共键,则希望将列值替换为 0

我想根据公共列合并多个文件,并希望在任何文件没有该公共列时添加 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

如果输出线的顺序很重要,那么调整很容易。

相关内容