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

时间:2017-04-17 16:10:36

标签: bash awk gawk

我正在使用awk合并多个(> 3)文件,我想保留标题。我发现之前的帖子完全符合我的要求,但我不太明白发生了什么。我希望有人能指引我,所以我可以从中学习! (我尝试评论原帖,但没有足够的声誉)

此代码

awk '{a[FNR]=((a[FNR])?a[FNR]FS$2:$0)}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$2:$0)}END{for(i=1;i<=FNR;i++) print a[i]}' f* > merge.txt

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

我认为在代码的第一部分中,正在建立一个数组。代码将运行并检查第一列id上的匹配记录,如果匹配,则附加第二列($2value并打印整个记录({{1 }})。

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

5 个答案:

答案 0 :(得分:4)

首先是数据:

file1              file2              file3

NR FNR $1 $2       NR FNR $1 $2       NR FNR $1 $2
================   ================   ================
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$2 : $0 )可以写成:

if(a[FNR]=="")           # actually if(a[FNR]=="" || a[FNR]==0)
    a[FNR]=$0            # a[FNR] is "id value1" when NR==1
else
    a[FNR]=a[FNR] FS $2  # 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.
}

答案 1 :(得分:4)

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

$ awk 'NR==FNR{a[FNR]=$0; next} {a[FNR] = a[FNR] OFS $2} 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]=$0;next} {a[FNR] = a[FNR] OFS $2} 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

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

$ awk '
    BEGIN { OFS="\t" }
    !($1 in a) { ids[++numIds]=$1 }
    { a[$1][ARGIND]=$2 }
    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以进行测试。

随意询问您是否有疑问,但我认为代码非常清楚。

答案 2 :(得分:2)

James has explained pretty well the awk logic in his answer

如果您正在寻找替代方案,那么基于paste的解决方案:

paste file1 file2 file3 | awk '{print $1, $2, $4, $6}' OFS='\t'

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

答案 3 :(得分:1)

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

?是三元运算符并且说,如果[FNR]中已存在某些东西,则将当前记录的2美元附加到那里,否则它为空,因此存储整个记录(即$ 0)。

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

if a[FNR] != ""
  a[FNR] = a[FNR] : FS : $2
else
  a[FNR] = $0

你可以看到第一个文件被删除后每条记录中的a,b,c可能是x,y,z,这个程序并不关心。它取第二个字段并附加到[2],[3]等

答案 4 :(得分:1)

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

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

(这些是列之间的标签)

或以同样的方式使用paste

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