从 awk 中的文件中提取前后字符到匹配的字符串

Extract preceding and trailing characters to a matched string from file in awk

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

我有第二个文件 search.txt,其中包含每行 50 个唯一字母,这些字母将在 seq.txt 中匹配一次。有 4000 个模式可以匹配。

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

我有一个脚本使用 grep 并且可以工作,但是它运行得非常慢,只执行前 100 个字符,并且用 echo 写出。我对 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 的制表符分隔文件。提前致谢!

一种方式

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 "\t\t";
        # or
        # printf "%-3s\t%3s%3s\n", , , ;
    }
}

运行 为 program.pl seq.txt,或将 seq.txt 的内容传递给它。

模式 .{0,$padding} 匹配任何字符 (.),最多 $padding 次(3 以上),如果发现模式 $patt$padding 更靠近字符串的开头(就像第一个 bcd,我将其添加到提供的示例中,我将使用什么问题)。 $patt.

之后的填充也是如此

在你的问题中,然后将 $padding 替换为 100。在每个模式前后使用 100 宽“填充”,当在比 100 更接近开头的位置找到模式时,如果位置小于 100,则所需的 \t 对齐可能会中断比制表符值(通常为 8)多 100。

这就是带有格式化打印 (printf) 的行的用途,以确保每个字段的宽度,无论打印的字符串长度如何。 (它被注释掉了,因为我们被告知没有模式会进入前 100 个字符或最后 100 个字符。)

如果匹配的模式确实不可能突破前 100 个位置或后 100 个位置,那么正则表达式可以简化为

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

请注意,如果 $patt 在 first/last $padding 字符中,那么这将不匹配。

该程序为每个 @patterns 启动正则表达式引擎,原则上这可能会引起性能问题(不是 运行 具有 4000 个模式的极少数,但此类要求往往会改变并且通常会增长)。但这是迄今为止最简单的方法,因为

  • 我们不知道模式在字符串中的分布情况,

  • 一个匹配项可能在另一个匹配项的 100 字符缓冲区内(我们没有被告知)

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


程序的输入(和输出)可以通过 Getopt::Long 使用命名的 command-line 参数以更好的方式组织,用于调用喜欢

program.pl --sequence seq.txt --search search.txt --padding 100

这里每个参数都可以是可选的,在文件中设置了默认值,参数名称可以缩短 and/or 给定额外的名称等。让我知道是否感兴趣

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([=10=],(i+b),n)
        s[k]=s[k] (s[k]==""?"":ORS) substr([=10=],i,(b+n+a))
    }
    next
}
([=10=] in s) {
    print s[[=10=]]
}' seq.txt search.txt

输出:

cdefghijk
mnopqrstu
rstuvwxyz

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

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

4000 个模式应该很容易,但如果你达到数十万个,也许你会想要优化。

这里没有 Perl 正则表达式,所以我从非标准 grep -P 切换到 POSIX-compatible 并且可能更高效 grep -E.

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

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

  • 加载字符串输入数据
  • 加载到数组模式
  • 遍历每个模式并在字符串中查找它
  • 从找到的匹配项中形成一个数组
  • 遍历匹配数组并打印结果

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

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})/  / && say for @found;
}

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

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

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

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

__DATA__
abcdefghijklmnopqrstuvwxyz

输出

cdefghijk
mnopqrstu
rstuvwxyz