如何在命令行中执行模式替换,在命令行中查找一个向量中的模式并从另一个向量中的相同位置替换?

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?

提问人:avery 提问时间:3/21/2023 最后编辑:brian d foyavery 更新时间:3/22/2023 访问量:111

问:

假设我在 .txt 文档中有两个字符向量列表,其中一个具有要替换的模式,另一个具有相同位置的替换。例如:

Pattern.txt
this
is
the
pattern
Replacement.txt
these
are
our
replacements

如果我有一个文件,特别是我的 gff,那么“this”的所有实例都将替换为“these”,依此类推。有没有办法使用 grep 或类似工具递归检查每个模式的任何实例并替换为相应的替换?如果这是重复,我深表歉意,我四处搜索,找不到我需要的东西。

与语言无关的 awk sed

评论

0赞 shellter 3/21/2023
注意 how 是 的子字符串。您需要先处理列表中较大的单词。如果它全部在一个文件中,那就容易多了,即 等。将其转换为 .祝你好运。isthisthis<TAB>thesesed 's/this/these/;s/is/are/;....;' file
0赞 zdim 3/21/2023
1)这些是作为词语,还是在任何地方发现的模式,换句话说,也应该被替换?(必须是一个词还是替换?2)“递归”是什么意思?isthis
0赞 user1934428 3/21/2023
如果模式列表的长度不仅仅是几个模式替换对,并且要处理的文件可以方便地放入内存中,我会将所有替换操作存储在内存中,并将文件标记为单词,然后遍历单词并应用替换过程,然后再写出新文本。
0赞 user1934428 3/21/2023
还要定义你所说的模式是什么意思?只是一个单词(即字符序列),还是一个正则表达式,或者......?

答:

1赞 Gilles Quénot 3/21/2023 #1

一种方法,使用 和 GNU

$ 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

你可以通过管道将其传递给bashsh

评论

0赞 Renaud Pacalet 3/21/2023
\bPattern.txt\b将匹配单词,但也,,...Pattern.txtPatternotxtPattern;txtPattern txt
1赞 Gilles Quénot 3/21/2023
是的,这可以通过OP进行调整,我做了最多的工作
1赞 Renaud Pacalet 3/21/2023 #2

在下文中,我们将空格称为空格、制表符、换行符、回车符、换形符或垂直制表符。我们将 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.txtpn

  • NR <= 2*n {r[m++] = $0; next}对第二个文件参数 () 执行相同的操作,并将替换字符串存储在 array 中。replacements.txtr

  • 最后一个块处理输入文件以过滤 ():{...}file.gff

    • split($0, a, /\s+/, s)将当前行拆分为用空格分隔的标记,将标记存储在数组中,将分隔符存储在数组中。as

    • for(i in a) {...}循环当前行的所有标记

    • for(j in p) if(a[i] == p[j]) { a[i] = r[j]; break }将当前行的当前标记与 中的每个标记进行比较。如果它们相等,则替换为相应的替换字符串 (),并且搜索停止 (),以避免在某些替换字符串包含某些“模式”时进行后续替换。a[i]pa[i]r[j]break

    • printf("%s%s", a[i], s[i])打印(可能被替换的)标记,后跟相应的分隔符。

1赞 zdim 3/21/2023 #3

一种方法是读取参考文件并形成模式替换对的哈希(字典),然后读取主文件并运行替换。可以一次读取文件并针对每个模式进行整体处理,也可以对所有模式进行逐行处理。在这篇文章中,两者都是用 Perl 完成的。

我假设一个替换单词,而不是其他单词中的模式。(所以必须是一个单独的词才能被替换,而不是作为 中的模式。然后我们需要检查整个单词(因此单词中的模式不匹配)。下面的代码按此运行。ouryourouryour

但是,如果这些模式也需要在其他单词中替换,那么我假设人们想先替换更长的模式(这样就不会从中窃取它)。然后,我们需要首先按长度对搜索的模式进行排序(以便在之前进行检查),如脚注所示。ouryouryourour

仍然存在问题,因为被替换的单词本身可以被替换(似乎是需要的,但我不确定)。我建议始终阐明完整的要求,即使一切看起来都很清楚。

如果文件足够小,我们可以将其“啜饮”成一个字符串,并遍历替换模式,一次一个替换整个文件

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;
}

评论

0赞 Renaud Pacalet 3/21/2023
你也许应该指定你使用哪种语言......
0赞 zdim 3/22/2023
@RenaudPacalet 当我回答这个问题时,这是唯一被标记的语言......(然后他们将其更改为“语言不可知”,即使 OP 通过标记它来要求特定的)。另一方面,如果读者甚至无法识别语言......但是谢谢你,我会在编辑时添加这一点
2赞 M. Nejat Aydin 3/21/2023 #4

使用 、 和 的解决方案,假设文件不包含特殊字符,这些字符在正则表达式和 :bashGNU sedpastesed

sed -f <(paste Pattern.txt Replacement.txt |
        sed 's%^%s/\\b%; s%\t%\\b/%; s%$%/g%') file.gff

评论

0赞 Renaud Pacalet 3/21/2023
好。不幸的是,包含...Pattern.txt.
1赞 M. Nejat Aydin 3/21/2023
@RenaudPacalet我认为是文件名,而不是文件内容中的一行,如果我正确理解了这个问题。Pattern.txt
0赞 Renaud Pacalet 3/21/2023
哦!也许,格式不明确,可能是每个列表中的第一项不是项目,而是文件名。这个问题绝对应该由 OP 编辑。
0赞 M. Nejat Aydin 3/21/2023
@RenaudPacalet同意。如果文件确实包含,则可以将其添加到表达式中。.s/[.]/\\&/gsed