提问人:avery 提问时间:3/21/2023 最后编辑:brian d foyavery 更新时间:3/22/2023 访问量:111
如何在命令行中执行模式替换,在命令行中查找一个向量中的模式并从另一个向量中的相同位置替换?
How do I perform pattern replacement in command line in which I find patterns in one vector and replace from same position in other vector?
问:
假设我在 .txt 文档中有两个字符向量列表,其中一个具有要替换的模式,另一个具有相同位置的替换。例如:
Pattern.txt
this
is
the
pattern
Replacement.txt
these
are
our
replacements
如果我有一个文件,特别是我的 gff,那么“this”的所有实例都将替换为“these”,依此类推。有没有办法使用 grep 或类似工具递归检查每个模式的任何实例并替换为相应的替换?如果这是重复,我深表歉意,我四处搜索,找不到我需要的东西。
答:
$ cat l1
Pattern.txt
this
is
the
pattern
$ cat l2
Replacement.txt
these
are
our
replacements
$ awk '
NR==FNR{a[NR]=$1;next}
{b[FNR]=$1}
END{for (i in a) { c++; print "sed \047s/\\b" a[c] "\\b/" b[c] "/\047 file"}}
' l1 l2
输出
sed -E 's/\bPattern.txt\b/Replacement.txt/' file
sed -E 's/\bthis\b/these/' file
sed -E 's/\bis\b/are/' file
sed -E 's/\bthe\b/our/' file
sed -E 's/\bpattern\b/replacements/' file
你可以通过管道将其传递给bash
sh
评论
\bPattern.txt\b
将匹配单词,但也,,...Pattern.txt
Patternotxt
Pattern;txt
Pattern txt
在下文中,我们将空格称为空格、制表符、换行符、回车符、换形符或垂直制表符。我们将 token 称为左侧空格(或行首)和右侧空格(或行尾)之间的连续非空格字符串。排队:
Pattern.txt #fo!o $ba&r@ 1234
有 4 个代币。
让我们假设,正如您的示例所示:
- 您所说的“模式”是要与输入文件中的标记进行比较的相等标记。
gff
- 这些标记存储在名为 的文件中,每行一个。
patterns.txt
- 您拥有的替换字符串与要替换的令牌的数量完全相同,存储在文件名中,每行一个。
replacements.txt
- “递归”的意思是“迭代”。
以下 GNU 脚本可以执行您想要的操作:awk
awk 'NR == FNR {p[n++] = $0; next} NR <= 2*n {r[m++] = $0; next} {
split($0, a, /\s+/, s)
for(i in a) {
for(j in p) if(a[i] == p[j]) { a[i] = r[j]; break }
printf("%s%s", a[i], s[i])
}
print ""
}' patterns.txt replacements.txt file.gff
解释:
NR == FNR {p[n++] = $0; next}
仅适用于第一个列出的文件参数 ()。它用要替换的标记填充数组,并将变量设置为它们的编号。patterns.txt
p
n
NR <= 2*n {r[m++] = $0; next}
对第二个文件参数 () 执行相同的操作,并将替换字符串存储在 array 中。replacements.txt
r
最后一个块处理输入文件以过滤 ():
{...}
file.gff
split($0, a, /\s+/, s)
将当前行拆分为用空格分隔的标记,将标记存储在数组中,将分隔符存储在数组中。a
s
for(i in a) {...}
循环当前行的所有标记。for(j in p) if(a[i] == p[j]) { a[i] = r[j]; break }
将当前行的当前标记与 中的每个标记进行比较。如果它们相等,则替换为相应的替换字符串 (),并且搜索停止 (),以避免在某些替换字符串包含某些“模式”时进行后续替换。a[i]
p
a[i]
r[j]
break
printf("%s%s", a[i], s[i])
打印(可能被替换的)标记,后跟相应的分隔符。
一种方法是读取参考文件并形成模式替换对的哈希(字典),然后读取主文件并运行替换。可以一次读取文件并针对每个模式进行整体处理,也可以对所有模式进行逐行处理。在这篇文章中,两者都是用 Perl 完成的。
我假设一个替换单词,而不是其他单词中的模式。(所以必须是一个单独的词才能被替换,而不是作为 中的模式。然后我们需要检查整个单词(因此单词中的模式不匹配)。下面的代码按此运行。our
your
our
your
但是,如果这些模式也需要在其他单词中替换,那么我假设人们想先替换更长的模式(这样就不会从中窃取它)。然后,我们需要首先按长度对搜索的模式进行排序(以便在之前进行检查),如脚注所示。our
your
your
our
仍然存在问题,因为被替换的单词本身可以被替换(似乎是需要的,但我不确定)。我建议始终阐明完整的要求,即使一切看起来都很清楚。
如果文件足够小,我们可以将其“啜饮”成一个字符串,并遍历替换模式,一次一个替换整个文件
use warnings;
use strict;
use feature 'say';
use Path::Tiny;
my $file = shift // die "Usage: $0 file\n";
my %repl;
my @pat = path("patt.txt")->lines({chomp=>1});
@repl{@pat} = path("repl.txt")->lines({chomp=>1});
my $file_content = path($file)->slurp;
for my $k (keys %repl) {
my $num_replaced = $file_content =~ s/\b$k\b/$repl{$k}/g;
say "Replaced '$k' at $num_replaced places" if $num_replaced;
}
say $file_content; # changed content
如果我只对单词被替换的假设被证明是不正确的,那么正则表达式就没有单词边界 (),因此允许在单词内部匹配和替换模式。然后,如果要替换较长的模式,首先我们需要搜索长度排序的模式。†\b
另一种方法是逐行读取文件,并对每一行进行所有替换
# Same up to reading the file
my %num_repl; # collect some stats
open my $fh, '<', $file or die "Can't open $file: $!";
while (<$fh>) {
for my $k (@key_search_order) {
my $n = s/\b$k\b/$repl{$k}/g;
$num_repl{$k} += $n if $n;
}
print;
}
say "Replaced '$_' at $num_repl{$_} places" for keys %num_repl;
†其他一切都是一样的
my @key_search_order =
map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [$_, length] }
keys %repl;
for my $k (@key_search_order) {
my $num_replaced = $file_content =~ s/$k/$repl{$k}/g;
say "Replaced '$k' at $num_replaced places" if $num_replaced;
}
评论
使用 、 和 的解决方案,假设文件不包含特殊字符,这些字符在正则表达式和 :bash
GNU sed
paste
sed
sed -f <(paste Pattern.txt Replacement.txt |
sed 's%^%s/\\b%; s%\t%\\b/%; s%$%/g%') file.gff
评论
Pattern.txt
.
Pattern.txt
.
s/[.]/\\&/g
sed
评论
is
this
this<TAB>these
sed 's/this/these/;s/is/are/;....;' file
is
this