如何用从子字符串索引的字典中获取的值替换子字符串

如何用从子字符串索引的字典中获取的值替换子字符串

我需要解析一个大文件,替换可能出现的子字符串,这些子字符串是与正则表达式匹配的可能出现的字符串的一部分,其值取自索引是相关子字符串的数组。

该文件是一个常规文本文件,即由换行符分隔的行,每行可以包含 ASCII 32 和 ASCII 126 之间的任意字符,基本上是除 C 语言环境中的控制字符之外的任何可打印字符。

精确匹配感兴趣的字符串的扩展正则表达式是\<prefix-[[:alnum:]]{2,}\>,而有问题的子字符串是破折号后面的任何内容。

使用样本(合成的)输入,例如:

# arbitrary number of comment lines of any length
:prefix-foo ; arbitrary strings
# arbitrary number of comment lines of any length foo -prefix-foo-
-bar -foo-xx arbitrary string -yet-more strings prefix-foo-bar MORE strings
YET more --STRINGS prefix-bar -prefix-foo-STRingS--
even MORE strings ; prefix -foo -yy--more-and-prefix-bar-and-more

并有一个示例字典,例如:

dictionary["foo"] = 2
dictionary["bar"] = 15

期望的输出是:

# arbitrary number of comment lines of any length
:prefix-2 ; arbitrary strings
# arbitrary number of comment lines of any length foo -prefix-2-
-bar -foo-xx arbitrary string -yet-more strings prefix-2-bar MORE strings
YET more --STRINGS prefix-15 -prefix-2-STRingS--
even MORE strings ; prefix -foo -yy--more-and-prefix-15-and-more

我认为这将是最好的工具,特别是因为它本身就能够通过替换单个字段awk来重写整个记录。因此我想出了以下脚本:$0$1...$n

#!/usr/bin/gawk -f

BEGIN {
    # first fill in dictionary
    while ("cmd-providing-dictionary" | getline) {
            dictionary[$1] = $2
    }
    close("cmd-providing-dictionary")
    # pattern that matches interesting fields
    field_regex = "\\<prefix-[[:alnum:]]{2,}\\>"
    # I don't care default splitting of line
    FS = OFS = ""
}
{
    # split line in fields as per regex
    if (patsplit($0, fields, field_regex, seps)) {
        FS = OFS = "-"
        # for each field, split it on dash character,
        # modify its substring as per dictionary,
        # and finally rebuild it
        for (fn in fields) {
            $0 = fields[fn]
            if ($2 in dictionary) {
                    $2 = dictionary[$2]
                    fields[fn] = $0
            }
        }
        FS = OFS = ""
        # clear whole record and rebuild it with
        # fields computed above + original separators
        $0 = ""
        for (fn in fields)
            $fn = seps[fn - 1] fields[fn]
        $(fn+1) = seps[fn]
    }
    print
}

尽管我对 awk 的使用能力不强,但上面的代码似乎足够快地完成了正确的工作,但看起来有点笨拙,感觉就像我强迫awk以一种不自然的方式做事。我想知道是否有更好的方法来获得相同的结果。或者也是一个更好的工具。

gsub()我的第一个想法是使用or进行简单的正则表达式替换gensub(),但我发现没有(干净的)方法来使用正则表达式的子表达式(这将是\<prefix-([[:alnum:]]{2,})\>)作为查找数组并在替换字符串中使用该值的键。另一方面,循环遍历所有字典键以始终应用所有gsubs 并不是真正可行,因为字典非常大,因此效率非常低。

答案1

只是为了进行比较,这里有一个非专家的 Perl 版本,它通过能够从替换中调用函数而获得了很多好处。就好像你可以说

gsub(regexp, call_function(matched_part), variable_to_change)

其中函数返回替换字符串。

#!/usr/bin/perl
use strict;
my %d;
sub fix{
  my ($prefix,$str) = @_;
  $str = $d{$str} if defined $d{$str};
  return "$prefix$str";
}
open(D,"dictionary") or die;
while(<D>){
  $d{$1} = $2 if $_ =~ m/^([^ ]+) ([^ \n]+)/;
}
close(D);
while(<>){
  $_ =~ s/\b(prefix-)([[:alnum:]]{2,})\b/fix($1,$2)/ge;
  print;
}

此处,替换命令全局$_ =~ s/regex/fix($1,$2)/ge更改当前行$_(g),并执行(e)替换字符串fix(),其中$1和是正则表达式的$2捕获组(在内)。()

相关内容