Bash 基于匹配列合并多个文件

Bash 基于匹配列合并多个文件,bash,awk,Bash,Awk,我有很多文件(我举了5个为例) 如果与第一个文件不匹配,则应在输出中追加0 file1 1001 1 2 1002 1 2 1003 3 5 1004 6 7 1005 8 9 1009 2 3 文件2 1002 7 1003 8 file3 1001 5 1002 3 file4 1002 10 1004 60 1007 4 file5 1001 102 1003 305 1005 809 所需输出 1001 1 2 0 5 0 102 1002 1 2 7 3 10 0 1

我有很多文件(我举了5个为例)

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

file1

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

1002 7
1003 8
file3

1001 5
1002 3
file4

1002 10
1004 60
1007  4
file5

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

提前感谢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"
这里非常有趣:


备注:在这种情况下,使用
auto
非常有用,但这不是问题的一部分。它是一个GNU扩展,是的一部分。纯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

有关使用GNU awk的
man join

的更多信息

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

注意:适用于上述情况和任何类型的值

使用GNU awk实现真正的多维数组并按以下方式排序:

$ 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
$cat tst.awk
FNR==1{numCols=colNr}
{
钥匙=1美元
对于(i=2;i wids[colNr]?lgth:wids[colNr])
}
}
结束{
numCols=colNr
PROCINFO[“排序在”]=“@ind\u num\u asc”
用于(输入VAL){
printf“%s”,键

对于(colNr=1;colNrAnd如果
file5
100666
,那么输出也会有
1000666
?嗨,詹姆斯,如果可能的话,请回答是的..是的,我想澄清一下
auto
是一个GNU扩展;如果OP使用的操作系统不使用GNU coreutils版本,那就不起作用了。(当然,这些文件需要在join字段中进行排序,它们在OP的示例中。)kvantour,惊人的解决方案一个解释多谢了,OP说她有“许多文件”这5只是一个例子,所以也许你可以展示一个脚本来概括N个文件的处理过程?太好了,谢谢,我认为这会更有用。多维数组是GNUextension@oguzismail说得对。这只是另一种方式。我一点也不赞同你的观点:)sjsam,tks很多
$ 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