【问题标题】:How to exclude lines in a file based on a range of values taken from a second file如何根据从第二个文件中获取的值范围排除文件中的行
【发布时间】:2020-06-19 08:31:31
【问题描述】:

我有一个包含值范围列表的文件:

2    4
6    9
13   14

还有一个如下所示的文件:

HiC_scaffold_1  1   26
HiC_scaffold_1  2   27
HiC_scaffold_1  3   27
HiC_scaffold_1  4   31
HiC_scaffold_1  5   34
HiC_scaffold_1  6   35
HiC_scaffold_1  7   37
HiC_scaffold_1  8   37
HiC_scaffold_1  9   38
HiC_scaffold_1  10  39
HiC_scaffold_1  11  39
HiC_scaffold_1  12  39
HiC_scaffold_1  13  39
HiC_scaffold_1  14  39
HiC_scaffold_1  15  42

我想从文件 2 中排除行,其中第 2 列的值落在文件 1 定义的范围内。理想的输出是:

HiC_scaffold_1  1   26
HiC_scaffold_1  5   34
HiC_scaffold_1  10  39
HiC_scaffold_1  11  39
HiC_scaffold_1  12  39
HiC_scaffold_1  15  42

我知道如何使用 awk 提取单个范围:

awk '$2 == "2", $2 == "4"' file2.txt

但我的文件 1 有很多范围值(行),我需要排除而不是提取与这些值对应的行。

【问题讨论】:

  • 看起来第二列等于其行的索引。总是这样吗?

标签: bash awk grep cut


【解决方案1】:

这是一个可怕的:

$ awk '
NR==FNR {                           # first file
    min[NR]=$1                      # store mins and maxes in pairs
    max[NR]=$2
    next
}
{                                   # second file
    for(i in min)                   
        if($2>=min[i]&&$2<=max[i])
            next
}1' ranges data

输出:

HiC_scaffold_1  1   26
HiC_scaffold_1  5   34
HiC_scaffold_1  10  39
HiC_scaffold_1  11  39
HiC_scaffold_1  12  39
HiC_scaffold_1  15  42

如果范围不是很大且整数值但数据很大,您可以制作值的排除映射以加快比较:

$ awk '
NR==FNR {                       # ranges file
    for(i=$1;i<=$2;ex[i++]);    # each value in the range goes to exclude hash
    next
}
!($2 in ex)' ranges data        # print if not found in ex hash

【讨论】:

  • 排除哈希效果很好。在较大的文件上运行时最多占用 2gb 的内存,但这是完全可以接受的。
【解决方案2】:

叛乱

如果file2.txt 的第二列始终等于其行的索引,则可以使用sed 修剪行。如果这不是您的情况,请参阅 awkception 段落。

sed $(sed 's/^\([0-9]*\)[[:space:]]*\([0-9]*\)/-e \1,\2d/' file1.txt) file2.txt

file1.txt 包含您的范围,file2.txt 是数据本身。

基本上,它构造了一个sed 调用,该调用将-e i,jd 表达式列表链接起来,这意味着它将删除ith 行和jth 行之间的行。

在您的示例中,sed 's/^\([0-9]*\)[[:space:]]*\([0-9]*\)/-e \1,\2d/' file1.txt 将输出 -e 2,4d -e 6,9d -e 13,14d,这是在 file2.txt 上调用 sed 的表达式列表。

最后它会调用:

sed -e 2,4d -e 6,9d -e 13,14d file2.txt

此命令删除第 2 和第 4 之间的所有行,以及第 6 和第 9 之间的所有行,以及第 13 和第 14 之间的所有行。

如果file2.txt 的第二列与它自己的行的索引匹配,显然它不起作用。

错觉

awk "{$(awk '{printf "if ($2>=%d && $2<=%d) next\n", $1, $2}' file1.txt)}1" file2.txt

即使第二列与其行的索引不匹配,此解决方案也有效。

该方法使用awk创建awk程序,就像sedsedception解决方案中创建sed表达式一样。

最后这会调用:

awk '{
if ($2>=2 && $2<=4) next
if ($2>=6 && $2<=9) next
if ($2>=13 && $2<=14) next
}1' file2.txt

需要注意的是,这个方案比sed慢很多。

【讨论】:

    【解决方案3】:

    如果您的范围不大:

    $ cat tst.awk
    NR==FNR {
        for (i=$1; i<=$2; i++) {
            bad[i]
        }
        next
    }
    !($2 in bad)
    
    $ awk -f tst.awk file1 file2
    HiC_scaffold_1  1   26
    HiC_scaffold_1  5   34
    HiC_scaffold_1  10  39
    HiC_scaffold_1  11  39
    HiC_scaffold_1  12  39
    HiC_scaffold_1  15  42
    

    【讨论】:

    • 运行良好,但不幸的是,我的一些数据文件超过 4000 万行,而相应的范围文件超过 100,000 行,在这种情况下这很困难。
    • 这在你的问题中是值得说明的。
    猜你喜欢
    • 1970-01-01
    • 2016-07-31
    • 1970-01-01
    • 2018-12-15
    • 2020-04-01
    • 2018-12-04
    • 1970-01-01
    • 1970-01-01
    • 2017-10-12
    相关资源
    最近更新 更多