有人可以通过这个 awk 代码来合并多个文件吗?

Can someone walk through this awk code for merging multiple files?

我正在使用 awk 合并多个 (>3) 个文件,我想保留 headers。我发现以前的 post 完全符合我的需要,但我不太明白发生了什么。我希望有人可以引导我完成它,以便我可以从中学习! (我尝试评论原版 post 但没有足够的声誉)

此代码

awk '{a[FNR]=((a[FNR])?a[FNR]FS:[=10=])}END{for(i=1;i<=FNR;i++) print a[i]}' f*

根据需要转换输入文件。请参阅下面的示例表格。

输入文件:

file1.txt:

id    value1
a     10
b     30
c     50

file2.txt:

id    value2
a     90
b     30
c     20

file3.txt:

id    value3
a     0
b     1
c     25

期望的输出

merge.txt:

id    value1  value2  value3
a     10      90      0
b     30      30      1
c     50      20      25

同样,这是代码

awk '{a[FNR]=((a[FNR])?a[FNR]FS:[=15=])}END{for(i=1;i<=FNR;i++) print a[i]}' f* > merge.txt

我无法理解代码的第一部分 {a[FNR]=((a[FNR])?a[FNR]FS:[=16=])},但理解代码第二部分中的循环。

我认为在代码的第一部分中,正在建立一个数组。代码遍历并检查第一列 id 上的匹配记录,如果匹配则追加第二列 (</code>) <code>value 并打印整个记录 ([=20 =]).

但是...我不明白开头的语法。何时确定第一列 id 在所有三个文件中都相同并且只添加第二列?

首先是数据:

file1              file2              file3

NR FNR         NR FNR         NR FNR  
================   ================   ================
1  1   id value1   5  1   id value2   9  1   id value3
2  2   a  10       6  2   a  90       10 2   a  0
3  3   b  30       7  3   b  30       11 3   b  1
4  4   c  50       8  4   c  20       12 4   c  25

第一部分:a[FNR]=( (a[FNR]) ? a[FNR]FS : [=13=] )可以写成:

if(a[FNR]=="")           # actually if(a[FNR]=="" || a[FNR]==0)
    a[FNR]=[=11=]            # a[FNR] is "id value1" when NR==1
else
    a[FNR]=a[FNR] FS   # a[FNR]="id value1" FS "value2" when NR==5

每个文件有4条记录,即。 FNR==4 在每个文件的最后一条记录上,尤其是最后一个文件,因为 FNR 的值在处理完最后一个文件后仍然存在:

END {                    # after hashing all record in all files
    for(i=1;i<=FNR;i++)  # i=1, 2, 3, 4
        print a[i]       # print "id value1 value value3" etc.
}

.

如果您正在寻找替代方案,这里有一个基于 paste 的解决方案:

paste file1 file2 file3 | awk '{print , , , }' OFS='\t'

id  value1  value2  value3
a   10  90  0
b   30  30  1
c   50  20  25

FNR 是相对于当前输入文件的记录数。所以file1、file2等中的行号http://www.thegeekstuff.com/2010/01/8-powerful-awk-built-in-variables-fs-ofs-rs-ors-nr-nf-filename-fnr/?ref=binfind.com/web

那个?是三元运算符,意思是,如果 a[FNR] 中已经有内容,则将当前记录的 $2 附加到那里的内容,否则它为空,因此存储整个记录(即 $0)。

可能有助于解释事情的伪代码:

if a[FNR] != ""
  a[FNR] = a[FNR] : FS : 
else
  a[FNR] = [=10=]

您可以看到在删除第一个文件后每条记录中的 a、b、c - 可能是 x、y、z 并且此程序不会在意。它采用第二个字段并附加到 a[2]、a[3] 等

您可以使用 awkpr 来执行此操作:

$ pr -mts$'\t' f1 <(awk '{print }' f2) <(awk '{print }' f3) 
id    value1    value2  value3
a     10    90  0
b     30    30  1
c     50    20  25

(这些是列之间的选项卡)

或以同样的方式使用paste

$ paste f1 <(awk '{print }' f2) <(awk '{print }' f3)
id    value1    value2  value3
a     10    90  0
b     30    30  1
c     50    20  25

该代码有错误且不必要地复杂,请改用此代码:

$ awk 'NR==FNR{a[FNR]=[=10=]; next} {a[FNR] = a[FNR] OFS } END{for (i=1;i<=FNR;i++) print a[i]}' file1 file2 file3
id    value1 value2 value3
a     10 90 0
b     30 30 1
c     50 20 25

如果您愿意,将输出通过管道传输到列 -t 以进行对齐:

$ awk 'NR==FNR{a[NR]=[=11=];next} {a[FNR] = a[FNR] OFS } END{for (i=1;i<=FNR;i++) print a[i]}' file1 file2 file3 | column -t
id  value1  value2  value3
a   10      90      0
b   30      30      1
c   50      20      25

如果您需要关闭 ids(例如,因为它们在文件中不同),那么它将是:

$ awk '
    BEGIN { OFS="\t" }
    !( in a) { ids[++numIds]= }
    { a[][ARGIND]= }
    END {
        for (i=1;i<=numIds;i++) {
            id = ids[i]
            printf "%s%s", id, OFS
            for (j=1;j<=ARGIND;j++) {
                printf "%s%s", a[id][j], (j<ARGIND ? OFS : ORS)
            }
        }
    }
' file1 file2 file3 | column -s$'\t' -t
id  value1  value2  value3
a   10      90      0
b   30      30      1
c   50              25
x           20

最后一个脚本将 GNU awk 用于多维数组,并且刚刚在输入文件 2 中将 c 更改为 x 以对其进行测试。

如果您有任何问题,请随时提出,但我认为代码非常清楚。