【问题标题】:Linux join muliple files with muliple columnsLinux加入多个文件的多列
【发布时间】:2014-10-23 17:16:11
【问题描述】:

我有一个问题需要合并多个不同的文件。

例如两个文件 *但列是相同的只是不同的值。 每个文件都有制表符分隔符。 按 ID 信息连接所有列。

第一个文件Test.txt

    ID     ID2     ID3    Name  Telephone       
    1       A       +     John    011
    1       B       -     Mike    012
    2       C       +     Sam    013
    3       A       -     Jena    014
    4       B       +     Peter    015

第二个文件Test2.txt

    ID     ID2     ID3    Name  Telephone       
    2       C       +     Henry    013
    3       A       -     Ho    014
    1       A       +     Jamy    011
    1       B       -     Mark    012
    4       B       +     Jung    015

然后是最终结果

    ID     ID2     ID3    Name  Telephone    Name  Telephone
    1       A       +     John    011        Jamy    011
    1       B       -     Mike    012        Mark    012
    2       C       +     Sam     013        Henry   013
    3       A       -     Jena    014        Ho      014
    4       B       +     Peter   015        Jung    015 

所以 combine 取决于 ID 1 ID2 ID3,

我尝试使用 join like 加入 -a1 -a2 -a3 Test1.txt Test2.txt > Test3.txt

类似这样,但性能和多文件连接存在问题 而且我不确定是否正确加入。

有人有最好的想法吗?

【问题讨论】:

    标签: linux join awk sed merge


    【解决方案1】:

    使用 GNU bash、GNU 核心实用程序和 GNU awk:

    join -j 5 <(sort -n Test.txt) <(sort -n Test2.txt) | awk '{print $2,$3,$4,$5,$1,$9,$1}' | column -t
    

    输出:

    ID  ID2  ID3  Name   Telephone  Name   Telephone
    1   A    +    John   011        Jamy   011
    1   B    -    Mike   012        Mark   012
    2   C    +    Sam    013        Henry  013
    3   A    -    Jena   014        Ho     014
    4   B    +    Peter  015        Jung   015
    

    【讨论】:

    • join 想要按词法排序的文件。
    【解决方案2】:
    awk -F"\t" -v OFS="\t" '
        {key = $1 SUBSEP $2 SUBSEP $3}
        FNR==NR {line[key]=$0; next} 
        key in line {print line[$1,$2,$3], $4, $5}
    ' Test.txt Test2.txt 
    
    ID  ID2 ID3 Name    Telephone   Name    Telephone
    2   C   +   Sam 013 Henry   013
    3   A   -   Jena    014 Ho  014
    1   A   +   John    011 Jamy    011
    1   B   -   Mike    012 Mark    012
    4   B   +   Peter   015 Jung    015
    

    如果要对输出进行排序,请将输出通过管道传输到 | { read header; echo "$header"; sort; }

    使用join,您只能加入一个字段。你必须求助于类似的东西

    join -j1 -t$'\t' <(sed 's/\t/:/;s/\t/:/' Test.txt|sort) \
                     <(sed 's/\t/:/;s/\t/:/' Test2.txt|sort) | 
    sed 's/:/\t/;s/:/\t/'
    

    然后,将标题留在底部(您可以使用| tac | { read header; echo "$header"; tac; } 修复)


    对评论的回应:

    awk -F"\t" '
        {key = $1 FS $2 FS $3}
        NR == 1 {header = key}
        !(key in result) {result[key] = $0; next}
        { for (i=4; i <= NF; i++) result[key] = result[key] FS $i }
        END {
            print result[header]
            delete result[header]
            PROCINFO["sorted_in"] = "@ind_str_asc"    # if using GNU awk
            for (key in result) print result[key]
        }
    ' Test.txt Test2.txt  # ... and other files
    

    【讨论】:

    • 太棒了几乎接近但正如我提到的它可能是多个文件,是否可以打印所有列一些灵活的命令而不是 $1,$2,$3,$4,$5 ?谢谢!!
    • key 相同 $1,$2,$3 但另一列可以是多个
    • 答案已更新。您应该更新您的问题,使要求更加明显(例如,向 Test2 添加一列)。
    • 您好,我添加了更多关于交集的问题。如果您有兴趣,请查看stackoverflow.com/questions/25652252/…
    【解决方案3】:

    使用awk,您可以为文件中看到的唯一键构建字符串。然后,您可以将输出通过管道传输到 column -t 以进行漂亮的打印。

    我使用第 1、2 和 3 列作为键,并将剩余的列从每个文件构建到原始行。

    awk --re-interval -F"\t" '
    { key = $1 SUBSEP $2 SUBSEP $3 }
    {
        if (line[key]) {
            sub (/([^\t]+\t+){3}/,"");
            line[key] = line[key] FS $0
        }
        else {
            line[key] = $0
        }
    }
    END {
         for (key in line) print line[key]
    }' file* | column -t | sort -r
    ID  ID2  ID3  Name   Telephone  Name   Telephone
    4   B    +    Peter  015        Jung   015
    3   A    -    Jena   014        Ho     014
    2   C    +    Sam    013        Henry  013
    1   B    -    Mike   012        Mark   012
    1   A    +    John   011        Jamy   011
    

    注意:如果您使用 GNU awk v4 或更高版本或 BSD awk,则无需指定 --re-interval


    如果您对perl 开放,那么您可以一次性完成:

    perl -F"\t" -lane '
        $" = "\t";
        $key = "@F[0..2]";
        push @{ $line{$key} }, @F[3..$#F];
    }{
        print join "\t", $_, @{ $line{$_} } for grep { $_ =~ /ID/ } sort keys %line;
        print join "\t", $_, @{ $line{$_} } for grep { not $_ =~ /ID/ } sort keys %line
    ' file*
    

    【讨论】:

    • 当我使用三个输入文件测试此解决方案时,我看到的输出与您显示的不同。在我的输出中,我看到每个输入文件都包含一次 ID 列。
    • @carl.anderson 您正在开发什么操作系统?我测试了 3 个文件,基本上一遍又一遍地提供相同的文件,它工作正常。我认为问题可能出在乱序打印的标题行上。
    • OSX。在 Ubuntu(精确)中,我的 awk 甚至没有 --re-interval 标志。无论如何,没关系。您是否使用 tab 分隔文件,如 OP 指定的那样?如果没有,那可能会导致 sub 命令什么也不做,包含超出您预期的内容。
    • @carl.anderson 已更新。对于 BSD awk,您不需要 --re-interval。我将其添加到答案中。
    猜你喜欢
    • 1970-01-01
    • 2012-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多