記事一覧へ
12/10/2024

AWKで日本語の踊り文字を解決したかったけどできなかった話

AWKで日本語の踊り文字を解決したかったけどできなかった話

本記事はAkatsuki Games Advent Calendarの10日目の記事です。

皆さん、踊り文字ってご存知でしょうか?

「々」 ←これです。

他にも、「〃」「ヽ」「ゝ」や、「く」を倒したような文字などがあります。 昔の小節ではよくこの文字が多用されていましたが、現代では「々」以外はあまり使われていないですね。

最近『楽園追放』を見てディストピアもののマイブームが来てしまい、『ガリバー旅行記』を読みたくなって青空文庫で読んでみたのですが、踊り文字が多すぎて読みづらかったので、どうにか置換してやろうと思いました。

そこで、シェル芸でおなじみのAWKを使って試みたのですが、できなかったというお話をこれからしようと思います。

踊り文字について

踊り文字をもう少し詳しく紹介します。

「々」

「ノ」「マ」をつなげたような文字なので、「ノマ点」と呼ばれています。 直前の漢字を繰り返すときに使用されます。

例: 代々木 → 代代木

また、あまり見たことが無いですが、Wikipedia - 踊り字によると次のような使い方もできるようです。

複複複線 → 複々々線(ふくふくふくせん) 小小小支川 → 小々々支川(しょうしょうしょうしせん)

二字以上の熟語を重ねるときにも使うこともある。

部分部分 → 部分々々 後手後手 → 後手々々 一歩一歩 → 一歩々々 南無阿弥陀仏南無阿弥陀仏 → 南無阿弥陀仏々々々々々々

まとめると、複数「々」が繰り返されるとき、直前に繰り返された数だけ漢字があればその熟語を繰り返し、直前に繰り返された数より少ない文字数の漢字があれば、直前の漢字を踊り字の数分繰り返すということでしょうか。

「ゝ」「ヽ」

一の字点と呼ばれているようです。 「ゝ」はひらがなを、「ヽ」はカタカナを繰り返すときに使われます。

例: あゝ → ああ, ミヽヽ → ミミミ

また、濁点を付加するときは「ゞ」「ヾ」を使います。逆に、濁音に濁点がないかなを続けるときは「ゝ」「ヽ」を使います。

例: ぶぶ漬け → ぶゞ漬け, づつ → づゝ

「/\」「/″\」

この文字は青空文庫でのみつかわれています。紙の本では「く」を倒したような文字で表示されています。 直前の単語をくりかえすときに使われます。

例: まあ/\ → まあまあ, 離れ/″\ → 離れ離れ

AWKについて

AWKは先述の通り、シェル芸でおなじみのやつです。

シェル芸に馴染みのない方にはあまり知られていないかもしれないですが、AWKはテキスト処理のためのプログラミング言語です。つまり、制御構文があります。

例えば、こんなことができます。

test.awk
#!/usr/bin/awk -f
 
{
    line = $0
    
    while(match(line, /([a-z])_R_/)){
        captured = substr(line, RSTART, 1)
        repeated = captured
        for(i = 1; i <= 50; i++){
            repeated = repeated captured
        }
        line = substr_replace(line, RSTART, 4, repeated)
    }
 
    print line
}
 
function substr_replace(str, start, len, replacement) {
    return substr(str, 1, start - 1) replacement substr(str, start + len)
}
実行結果
$ echo "hello_R_" | awk -f test.awk
hellooooooooooooooooooooooooooooooooooooooooooooooooooo

この例では、_R_の直前の文字を50回繰り返すという処理をしています。

踊り文字を解決する

さて、では早速AWKを使った踊り文字の解決にチャレンジしてみましょう。

AWKには、gensubという関数があり、これを使うと正規表現でマッチした文字列を後方参照を用いて置換できます。後方参照するには\\1を使えばいいので、\\1\\1とすれば踊り文字の直前の文字が繰り返されるはずです。 余裕しょ。ということでとりあえず「ゝ」の置換をしてみます。

test.awk
#!/usr/bin/awk -f
{
    line = $0
    line = gensub(/([-])ゝ/, "\\1\\1", "G", line)
    print line
}
実行結果
$ echo "あゝ" | awk -f test.awk

ナンデェェ。・゚(ノД`ヾ)゚・。ェェ

なぜか繰り返されず、踊り文字が消えてしまいました。なぜでしょうか?hexdump -Cでバイトコードを確認してみます。

hexdump結果
$ echo "あゝ" | awk -f test.awk | hexdump -C
00000000  e3 81 82 82 0a                                    |.....|
00000005

「あ」のバイトコードはe3 81 82です。しかし、gensubで置換された後のバイトコードはe3 81 82 82となってしまいました。(0aはNUL文字です) この挙動を見る限り、末尾の82のみがキャプチャされ、繰り返されてしまったようです。

試しに、match関数がどのように動作するか確認してみます。

test.awk
#!/usr/bin/awk -f
{
    line = $0
    match(line, /([-])ゝ/)
    print RSTART, RLENGTH
}
実行結果
$ echo "あゝ" | awk -f test.awk
3 4

RSTARTはマッチした文字列の開始位置、RLENGTHはマッチした文字列の長さを表しています。(ちなみに、AWKのインデックスは1から始まります)

3バイト目から4バイト分がマッチしているので、「あゝ」のバイトコードe3 81 82 e3 82 9dのうち、後ろ4文字の82 e3 82 9dのみがマッチしているようです。

とりあえずひらがなは3バイトで表現されるので、3バイト目からマッチするという前提で、「ゝ」を置換するスクリプトを書いてみましょう。gensubでは役不足なので、与えたオフセットから与えた長さ分削除してそこに文字列を挿入する関数を作ってみます。

test.awk
#!/usr/bin/awk -f
{
    line = $0
    
    while(match(line, /([-])ゝ/)){
        captured = substr(line, RSTART - 2, 3)
        repeated = captured captured
        line = substr_replace(line, RSTART-2, 6, repeated)
    }
 
    print line
}
 
function substr_replace(str, start, len, replacement) {
    return substr(str, 1, start - 1) replacement substr(str, start + len)
}
実行結果
$ echo "あゝ" | awk -f test.awk
ああ

やっとうまくいきました。

結論: 難しい

とりあえずひらがなはバイト長を決め打つことで無理やり解決することができました。しかし、特に漢字はバイト長が可変なので、この方法では解決できません。

AWKの仕様上(仕様なのか?)マルチバイト文字の末尾をマッチ箇所としてしまう限り、踊り文字を解決することは難しそうです。

gawkはマルチバイト文字も正しく処理するという記事をいくつか読んだのですが、せめて文字の頭からマッチしてくれないと文字種も判別できなさそうです。

というわけで、AWKで踊り文字を解決するのは難しいという結論に至りました。次回は(やる気が出たら)SQLかPerlで挑戦してみようと思います!

次回予告

なんと、原因が分かってしまったため、12/25のエントリで今度こそAWKで踊り文字を解決した話を書きます。お楽しみに!


書いた人

木瓜丸

Webエンジニア。2022年に「木瓜丸屋」を開業し、個人開発をしています。

その他プロフィールをチェック