从 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
我有一个很大的 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