하위 문자열을 해당 하위 문자열로 인덱싱된 사전에서 얻은 값으로 바꾸는 방법

하위 문자열을 해당 하위 문자열로 인덱싱된 사전에서 얻은 값으로 바꾸는 방법

정규식과 일치하는 가능한 문자열의 일부인 가능한 하위 문자열을 인덱스가 관련 하위 문자열인 배열에서 가져온 값으로 대체하여 대용량 파일을 구문 분석해야 합니다.

파일은 일반 텍스트 파일입니다. 즉, 줄 바꿈으로 구분된 줄이며 각 줄에는 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,})\>)을 조회 배열로 사용하고 이를 대체 문자열에서 사용하는 (깨끗한) 방법을 찾지 못했습니다. 값. 반면에 모든 사전 키를 반복하여 all gsub을 항상 적용하는 것은 실제로 실현 가능하지 않습니다. 사전이 매우 크고 따라서 매우 비효율적이기 때문입니다.

답변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()

관련 정보