从awk中的文件中将前字符和尾字符提取到匹配的字符串

时间:2020-11-09 07:52:10

标签: perl awk grep

我有一个很大的字母字符串文件seq.txt,已解包,超过200,000个字符。没有空格,数字等,只有a-z。

我有第二个文件search.txt,其中有50个唯一字母的行,它们在seq.txt中匹配一次。有4000种样式可以匹配。

我希望能够找到每个模式(文件search.txt中的行),然后获得模式匹配之前的100个字符和之后的100个字符。

我有一个脚本,该脚本使用grep可以工作,但是运行速度非常慢,只执行前100个字符,并以回显方式写出。我对awk或perl的知识不足,无法在线解释可能适用的脚本,所以我希望这里有人!

cat search.txt | while read p; do echo "grep -zoP '.{0,100}$p' seq.txt | sed G"; done > do.grep

带有所需输出的简单示例:

>head seq.txt    
abcdefghijklmnopqrstuvwxyz

>head search.txt
fgh
pqr
uvw

>head desiredoutput.txt
cdefghijk
mnopqrstu
rstuvwxyz

最好的结果是将100 characters before \t matched pattern \t 100 characters after的一个制表符分隔的文件。预先谢谢你!

4 个答案:

答案 0 :(得分:4)

一种方式

use warnings;
use strict;
use feature 'say';

my $string;

# Read submitted files line by line (or STDIN if @ARGV is empty)
while (<>) {
    chomp;
    $string = $_;    
    last;          # just in case, as we need ONE line
}
# $string = q(abcdefghijklmnopqrstuvwxyz);   # test

my $padding = 3;  # for the given test sample

my @patterns = do { 
    my $search_file = 'search.txt';
    open my $fh, '<', $search_file or die "Can't open $search_file: $!";
    <$fh>;
};
chomp @patterns;
# my @patterns = qw(bcd fgh pqr uvw);  # test

foreach my $patt (@patterns) {
    if ( $string =~ m/(.{0,$padding}) ($patt) (.{0,$padding})/x ) {
        say "$1\t$2\t$3";
        # or
        # printf "%-3s\t%3s%3s\n", $1, $2, $3;
    }
}

program.pl seq.txt的身份运行,或将seq.txt的内容通过管道传递给它。

模式.{0,$padding}与任何字符(.)匹配,最多 $padding次(上面的3),这与我使用的情况相同模式$patt位于比$padding更靠近字符串开头的位置(就像我在问题中提供的示例中添加的第一个bcd一样)。 $patt之后的填充也是如此。

如果遇到问题,请将$padding替换为100。在每个样式之前和之后使用100宽的“填充”,如果在比100更靠近开始位置的位置找到样式,则如果\t对齐方式小于比制表符的值大100(通常为8)。

这就是格式化打印(printf)所在的行的用途,以确保每个字段的宽度,而不管所打印字符串的长度如何。 (由于我们被告知没有模式进入前100个或最后一个字符,因此已将其注释掉。)

如果确实没有匹配的样式突破前100个位置或最后一个位置的机会,则可以将正则表达式简化为

/(.{$padding}) ($patt) (.{$padding})/x

请注意,如果$patt 位于的第一个/最后一个字符内,则这将不匹配。

该程序为$padding中的每一个启动正则表达式引擎,原则上可能会引起性能问题(不是针对极少数4000种模式的运行,但是这种要求往往会发生变化并且通常会增长)。但这是迄今为止最简单的方法

  • 我们不知道模式如何在字符串中分布,并且

  • 一个匹配项可能位于另一个匹配项的100个字符内(我们不会告诉其他人)

如果此方法存在性能问题,请更新。


可以通过@patterns使用命名的命令行参数以更好的方式组织程序的输入(和输出),以进行类似

的调用
Getopt::Long

这里每个参数都是可选的,文件中设置了默认值,参数名称可能会缩短和/或指定其他名称,等等。让我知道这是否有意义

答案 1 :(得分:2)

awk中的一个。 -v b=3是上下文前后的长度-v a=3是上下文前后的长度,-v n=3是匹配长度,始终保持不变。它将seq.txt的所有子字符串散列到内存中,以便根据seq.txt的大小使用它,并且您可能希望使用top来跟踪消耗情况,例如:abcdefghij -> s["def"]="abcdefghi" s["efg"]="bcdefghij"

$ awk -v b=3 -v a=3 -v n=3 '
NR==FNR {
    e=length()-(n+a-1)
    for(i=1;i<=e;i++) {
        k=substr($0,(i+b),n)
        s[k]=s[k] (s[k]==""?"":ORS) substr($0,i,(b+n+a))
    }
    next
}
($0 in s) {
    print s[$0]
}' seq.txt search.txt

输出:

cdefghijk
mnopqrstu
rstuvwxyz

答案 2 :(得分:1)

您可以告诉grep一次性搜索所有模式。

sed 's/.*/.{0,100}&.{0,100}/' search.txt |
grep -zoEf - seq.txt |
sed G >do.grep

4000种模式应该很容易实现,尽管如果成千上万种,也许您将需要优化。

这里没有Perl正则表达式,因此我从非标准grep -P切换到POSIX兼容且可能更高效的grep -E

周围的上下文将使用它打印的所有文本,因此与前一个字符相距100个字符以内的任何匹配项都不会被打印。

答案 3 :(得分:1)

您可以尝试以下解决问题的方法:

  • 加载字符串输入数据
  • 加载到阵列模式中
  • 遍历每个模式并在字符串中查找
  • 根据找到的匹配项形成数组
  • 遍历 matches 数组并显示结果

注意:由于缺少输入数据,因此未测试代码

use strict;
use warnings;
use feature 'say';

my $fname_s = 'seq.txt';
my $fname_p = 'search.txt';
    
open my $fh, '<', $fname_s
    or die "Couldn't open $fname_s";
my $data = do { local $/; <$fh> };
close $fh;

open my $fh, '<', $fname_p
    or die "Couln't open $fname_p";
my @patterns = <$fh>;
close $fh;

chomp @patterns;

for ( @patterns ) {
    my @found = $data =~ s/(.{100}$_.{100})/g;
    s/(.{100})(.{50})(.{100})/$1 $2 $3/ && say for @found;
}

提供的测试数据的测试代码(稍后添加)

use strict;
use warnings;
use feature 'say';

my @pat  = qw/fgh pqr uvw/;
my $data = do { local $/; <DATA> }; 

for( @pat ) {
    say $1 if $data =~ /(.{3}$_.{3})/;
}

__DATA__
abcdefghijklmnopqrstuvwxyz

输出

cdefghijk
mnopqrstu
rstuvwxyz