囲まれた文字列の置換 (その 2)

前の囲まれた文字列の置換では match() 関数が正規表現にマッチする最長一致を行うため、複数個ある場合に対応していませんでした。 「最短一致の match() 関数のようなものを作ることも可能です」と書いたので、これを作って再度挑戦してみます。

awk の書籍の中には最短一致を行うものを紹介しているものもありますが、ここではマッチするまで 1 文字づつ文字列を増やしてマッチしたら RSTART, RLENGTH に該当するものとして LSTART, LLENGTH をセットしています。 具体的なコードは以下のとおりです。

#! /usr/bin/gawk -f
# random_char_3.awk

{
    while (lmatch($0, "@<.*@>")) {
        new_str = "";
        for (i = 1; i <= 10; i++) {
            new_str = new_str sprintf("%c", int(rand() * 26) + 65);
        }
        print substr($0, LSTART + 2, LLENGTH - 4), new_str > output1;
        $0 = substr($0, 1, LSTART - 1) \
             "@\034<" new_str "@\034>" \
             substr($0, LSTART + LLENGTH);
    }
    gsub(/\034/, "", $0);
    print $0 > output2;
}

# lmatch():     最左最短一致を行う match() 関数
#   in:     str:    文字列
#   in:     reg:    正規表現 (ただし、ダブルクォートで区切る)
#   out:    マッチすれば 1 を返し、マッチしなければ 0 を返す
#           マッチすれば、RSTART, RLENGTH に該当するものとして
#           LSTART, LLENGTH を大域変数としてセットする
function lmatch(str, reg,    tmp_str, i) {
    for (i = 1; i <= length(str); i++) {
        if (substr(str, 1, i) ~ reg) {
            tmp_str = substr(str, 1, i);
            sub(reg, "", tmp_str);
            LSTART = length(tmp_str) + 1;
            LLENGTH = i - length(tmp_str);
            return 1;
        }
    }
    return 0;
}

lmatch() 関数の中で match() を使うと RSTART, RLENGTH がセットされていた場合に破壊してしまいますので、match() 関数は使えません。 また、置換後に "@<", "@>" に再度マッチしてしまうため、"@\034<", "@\034>" に置換して、最後に "\034" を削除しています。 "\034" は多次元配列の際に連接される文字列ですので、多分対象のテキストを破壊しないであろうことを前提にしています。

さて、実行してみます。

$ nawk -v output1="output1.txt" -v output2="output2.txt" -f random_char_3.awk random_char.in

$ cat output1.txt
aaa VKUUXFITHO
ddd MQJNYXQSDP
fff AGDUEKDCZF
iii NVPHQNMZHU
kkk NUKXHJVXBY

$ cat output2.txt
@<VKUUXFITHO@>bbb
ccc@<MQJNYXQSDP@>
eee@<AGDUEKDCZF@>ggg
hhh@<NVPHQNMZHU@>jjj@<NUKXHJVXBY@>lll

というようにうまく最短マッチしてくれました。

tag_nawk.pngtag_nawk.pngtag_nawk.pngtag_nawk.png