【问题标题】:Merging many files based on matching column根据匹配列合并多个文件
【发布时间】:2019-04-30 10:37:44
【问题描述】:

我有很多文件(我发布了 5 个作为示例)

如果与第一个文件不匹配,则应在输出中附加 0

文件1

1001 1 2
1002 1 2
1003 3 5
1004 6 7
1005 8 9
1009 2 3

文件2

1002 7
1003 8

文件3

1001 5
1002 3

文件4

1002 10
1004 60
1007  4

文件5

1001 102
1003 305
1005 809

需要的输出

1001 1 2 0 5  0 102
1002 1 2 7 3 10   0
1003 3 5 8 0  0 305
1004 6 7 0 0 60   0
1005 8 9 0 0  0 809
1007 0 0 0 0  4   0
1009 2 3 0 0  0   0

使用下面的代码我可以合并两个文件,但是如何合并所有

awk 'FNR==NR{a[$1]=$2;next}{print $0,a[$1]?a[$1]:"0"}' file2 file1

1001 1 2 0
1002 1 2 7
1003 3 5 8
1004 6 7 0
1005 8 9 0

提前致谢

【问题讨论】:

  • 如果file51006 666 输出也会有1006 0 0 0 0 0 666?
  • 嗨詹姆斯,是的,如果可能的话,请......是的

标签: bash awk


【解决方案1】:

GNU 加入救援!

$ join -a1 -a2 -e '0' -o auto file1 file2 \
  | join -a1 -a2 -e '0' -o auto - file3   \
  | join -a1 -a2 -e '0' -o auto - file4   \
  | join -a1 -a2 -e '0' -o auto - file5

选项-a1-a2 告诉join 插入缺失的字段。 -e '0' 告诉它用零替换它们。输出由-o auto 指定,它假定采用所有字段。

当有大量文件时,不能使用管道构造,但可以使用简单的 for 循环:

out=output
tmp=$(mktemp)
[[ -e "$out" ]] && rm -rf "$out" || touch "$out"
for file in f*; do
    join -a1 -a2 -e0 -o auto "$out" "$file" > "$tmp"
    mv "$tmp" "$out"
done
cat "$out"

或者如果你真的喜欢管道:

pipeline="cat /dev/null"
for file in f*; do pipeline="$pipeline | join -a1 -a2 -e0 -o auto - $file"; done
eval "$pipeline"

非常感兴趣:Is there a limit on how many pipes I can use?


备注:auto 在这种情况下非常有用,但不是POSIX standard 的一部分。它是一个 GNU 扩展,是 GNU coreutils 的一部分。纯 POSIX 版本会读起来有点麻烦:

$ join -a1 -a2 -e '0' -o 0 1.2 2.2 file1 file2 \
  | join -a1 -a2 -e '0' -o 0 1.2 1.3 2.2 - file3 \
  | join -a1 -a2 -e '0' -o 0 1.2 1.3 1.4 2.2 - file4 \
  | join -a1 -a2 -e '0' -o 0 1.2 1.3 1.4 1.5 2.2 - file5

更多关于man join的信息

【讨论】:

  • 可能想澄清auto 是一个GNU 扩展;如果 OP 使用的操作系统不使用 GNU coreutils 版本,那将无法正常工作。 (当然,文件需要在连接字段上进行排序,它们在 OP 的示例中。)
  • kvantour,非常好的解决方案,非常感谢
  • OP 说她有“很多文件”,5 个只是一个例子,所以也许你可以展示一个脚本来概括 N 个文件的过程?
  • 太好了,谢谢,我认为这会更有用。
【解决方案2】:

使用 GNU awk 实现真正的多维数组和 sorted_in:

$ cat tst.awk
FNR==1 { numCols = colNr }
{
    key = $1
    for (i=2; i<=NF; i++) {
        colNr = numCols + i - 1
        val   = $i
        lgth  = length(val)
        vals[key][colNr] = val
        wids[colNr] = (lgth > wids[colNr] ? lgth : wids[colNr])
    }
}
END {
    numCols = colNr
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (key in vals) {
        printf "%s", key
        for (colNr=1; colNr<=numCols; colNr++) {
            printf "%s%*d", OFS, wids[colNr], vals[key][colNr]
        }
        print ""
    }
}

$ awk -f tst.awk file*
1001 1 2 0 5  0 102
1002 1 2 7 3 10   0
1003 3 5 8 0  0 305
1004 6 7 0 0 60   0
1005 8 9 0 0  0 809
1007 0 0 0 0  4   0
1009 2 3 0 0  0   0

【讨论】:

    【解决方案3】:

    使用GNU awk

    awk '
    NR>FNR && FNR==1{
    colcount+=cols
    }
    {
    for(i=2;i<=NF;i++){
      rec[$1][colcount+i-1]=$i
    }
    } 
    {
    cols=NF-1
    }
    END{
      colcount++
      for(ind in rec){
        printf "%s%s",ind,OFS
        for(i=1;i<=colcount;i++){
          printf "%s%s",rec[ind][i]?rec[ind][i]:0,OFS
        }
        print ""
      }
    }' file{1..5} | sort -k1 | column -t
    

    输出

    1001  1  2  0  5  0   102
    1002  1  2  7  3  10  0
    1003  3  5  8  0  0   305
    1004  6  7  0  0  60  0
    1005  8  9  0  0  0   809
    1006  0  0  0  0  0   666
    

    注意:适用于提到的here 和任何类型的值。

    【讨论】:

    • 多维数组是 GNU 扩展
    • @oguzismail 说得对。这只是另一种方式。我一点也不赞同你的:)
    • sjsam,tks 很多
    猜你喜欢
    • 2019-06-28
    • 1970-01-01
    • 2022-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-17
    相关资源
    最近更新 更多