0

我有两个如下所示的文件,它们是制表符分隔的:

档案A

chr1   123 aa b c d
chr1   234 a  b c d
chr1   345 aa b c d
chr1   456 a  b c d
....

文件 B

xxxx  abcd    chr1   123    aa    c    d    e
yyyy  defg    chr1   345    aa    e    f    g
...

我想加入基于 2 列与“chr1”,“123”的两个文件,并将文件 B 中的前两列添加到文件 A。这是使用

awk 'NR==FNR{a[$3,$4]=$1OFS$2;next}{$7=a[$1,$2];print}' OFS='\t' fileb filea

输出:

chr1   123    aa    b    c    d    xxxx    abcd
chr1   234    a     b    c    d
chr1   345    aa    b    c    d    yyyy    defg
chr1   456    a     b    c    d

但是对于真实数据,fileb 太大并且返回错误:“无法分配 6400 字节的内存(无法分配内存)”。有人可以提供另一种方法来执行此操作,以便以较小的部分读取文件。

4

3 回答 3

3

这将速度换成内存:

$ cat tst.awk                
BEGIN{
    FS=OFS="\t"
    lookup = ARGV[--ARGC]
    delete ARGV[ARGC]
}
{
    found = 0
    while ( !found && ((getline str < lookup) > 0) ) {
        split(str,arr)
        if ( ($1 == arr[3]) && ($2 == arr[4]) ) {
            $0 = $0 OFS arr[1] OFS arr[2]
            found = 1
        }
    }
    close(lookup)
    print
}
$ gawk -f tst.awk fileA fileB
chr1    123     aa      b       c       d       xxxx    abcd
chr1    234     a       b       c       d
chr1    345     aa      b       c       d       yyyy    defg
chr1    456     a       b       c       d

它使用接近于零的内存,因为它不会在内部存储任何值,但它会很慢,因为对于 fileA 中的每一行,它会读取 fileB 中的每一行,直到找到匹配项,而不是您已经尝试读取的内容来自 fileB 的所有行并将它们存储为由字段 3 和 4 键入的数组元素,在这种情况下,它将是 fileA 的每一行的内部哈希查找,而不是外部线性搜索。

如果 fileA 中的许多键在 fileB 中不存在,那么您可以通过在第 3 和第 4 个字段上对 fileB 进行排序,然后将 getline 循环中的测试更改为以下内容来大大加快速度:

        if ( ($1 FS $2) == (arr[3] FS arr[4]) ) {
            $0 = $0 OFS arr[1] OFS arr[2]
            found = 1
        }
        else (if ($1 FS $2) < (arr[3] FS arr[4]) ) {
            found = 1
        }

您可以找出正确的逻辑 - 希望您知道当您从 fileA 中查找的值可能存在于已排序的 fileB 中时,您想要停止循环。

于 2013-09-12T20:24:54.843 回答
1

一种快速而肮脏的技术是操纵您的输入数据并使用join

$ awk '{print $3"-"$4,$1,$2}' fileb | sort > fileb2
$ awk '{print $1"-"$2,$3,$4,$5}' filea | sort > filea2
$ join -a1 filea2 fileb2
chr1-123 aa b c xxxx abcd
chr1-234 a b c
chr1-345 aa b c yyyy defg
chr1-456 a b c

如有必要,您可以去掉第一列中的 -。请注意,这并不可靠,购买可能就足够了。 join可能需要更少的内存awk并且能够处理输入......或者它可能不会!

于 2013-09-12T13:47:29.730 回答
0

你可以试试这段代码:

#!/usr/bin/awk -f

BEGIN {
    file1 = ARGV[1]
    file2 = ARGV[2]
    LIMIT = 1000
    OFS = "\t"
    i = 0
    while ((getline < file2) > 0) {
        key = $3 "\x1c" $4
        if (!(key in a)) {
            a[key] = $1 OFS $2
            if (i == LIMIT) {
                break
            }
        }
    }
    while ((getline < file1) > 0) {
        key = $1 "\x1c" $2
        if (key in a) {
            $7 = a[key]
            print
            delete a[key]
            while ((getline < file2) > 0) {
                key = $3 "\x1c" $4
                if (!(key in a)) {
                    a[key] = $1 OFS $2
                    break
                }
            }
        } else {
            $7 = ""
            print
        }
    }
    exit 0
}

用法:awk -f script.awk filea fileb

于 2013-09-12T18:02:09.353 回答