【问题标题】:Can someone walk through this awk code for merging multiple files?有人可以通过这个 awk 代码来合并多个文件吗?
【发布时间】:2017-09-13 06:51:24
【问题描述】:

我正在使用 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

想要的输出

合并.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 上的匹配记录,如果有匹配,则附加第二列 ($2) value 并打印整个记录 ($0)。

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

【问题讨论】:

  • 该代码不使用或引用id,它根据行的顺序而不是每行第一个字段的值来执行所有操作。如果您需要检查ids,因为它们可能因文件而异,那么您需要不同的解决方案。
  • 啊。很高兴知道。我确实希望程序检查 ID。事实证明,对于我正在使用的当前文件,id 是相同的,但在未来,情况并非总是如此。如果我想合并引用 id,最好的解决方案是什么?将a[FNR])?a[FNR]FS$2:$0) 中的FNR 替换为$1? -- NVM 在下面看到了您的代码。谢谢!

标签: bash awk gawk


【解决方案1】:

首先是数据:

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.
}

【讨论】:

    【解决方案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】:

      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 : $2
      else
        a[FNR] = $0
      

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

      【讨论】:

        【解决方案4】:

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

        $ 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
        

        【讨论】:

        • fyi:默认是tab,所以-mts就足够了
        【解决方案5】:

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

        $ 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
        

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

        $ 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 以对其进行测试。

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

        【讨论】:

        • 这很棒。谢谢!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-16
        • 2023-03-06
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        相关资源
        最近更新 更多