【Mac】なろう記法の自動化 2

2019年3月3日

なろう記法を自動化で「小説家になろう」のやり方でルビを振るというシェルスクリプトを書いた。
 前回のスクリプトにはひとつ、重大な欠点があった。「美味しい」を「おいしい」と読ませたいのに、「美味」のふりがなが「びみ」になってしまうのだ。
 これを修正するため、いろいろ考えて、入力した文章の漢字ぜんぶにルビを振る、というシェルスクリプトを作ってみた。

シェルスクリプト

苦労して作りあげたのが以下である。長くなってしまった。
 あまり長い文章は処理できないと思う。
 未知語の場合は最初からルビを振らない。
 前回と同じく、mecab と nkf の力をおおいに借りている。

📝 furigana.sh
#!/bin/bash

function getKanji() {
	local kan=`echo $1 | nkf --hiragana`
	local moji=`echo $2 | nkf --hiragana | grep -o '.'`
	local chara=(`echo $moji`)
	for c in "${chara[@]}"
	do
		kan=`echo $kan | sed -e "s/$c//"`
	done
	echo $kan
}

str="$1"
wakati=`echo "$str" | mecab -O wakati`

arr=(`echo $wakati`)

for word in "${arr[@]}"
do
	michigo=`echo "$word" | mecab -x "未知語" | grep '未知語'`
	if test -n "$michigo"; then
		arr2+=("$word")
		continue
	fi
	if echo "$word" | grep -sqi '[a-z][0-9]'; then
		arr2+=("$word")
		continue
	fi
	
	yomi=`echo "$word" | mecab -O yomi | nkf --hiragana -w`
	katakana=`echo "$word" | mecab -O yomi | nkf -w`
	if test "$word" != "$yomi" -a "$word" != "$katakana"; then
		kanji=`getKanji $word $yomi`
		okuri=`getKanji $word $kanji`

		if test -z $okuri; then
			jukugo=`echo '|'$kanji'《'$yomi'》'`
			arr2+=("$jukugo")
		else
			if echo $word | grep -sq "$kanji"; then
				furi=`getKanji $yomi $okuri`
				rubi=`echo '|'$kanji'《'$furi'》'`
				furigana=`echo $word | sed -e "s/$kanji/$rubi/g"`
				arr2+=("$furigana")
			else #「置き引き」「女の子」「止まり木」問題
				okuCha=`echo $okuri | nkf --hiragana | grep -o '.'`
				okuriChara=(`echo $okuCha`)
				kanAr=`echo $word | nkf --hiragana`
				furiAr=$yomi
				for oc in "${okuriChara[@]}"
				do
					kanAr=`echo $kanAr | tr "$oc" '\n'`
					furiAr=`echo $furiAr | tr "$oc" '\n'`
				done
				kanjiAr=(`echo $kanAr`)
				furiganaAr=(`echo $furiAr`)
				furigana=`echo $word | nkf --hiragana`
				int=0
				for ht in "${kanjiAr[@]}"
				do
					rubi=`echo '|'$ht'《'${furiganaAr[$int]}'》'`
					furigana=`echo $furigana | sed -e "s/$ht/$rubi/"`
					int=`echo $((int + 1))`
				done
				arr2+=("$furigana")
			fi
			
		fi
	else
		arr2+=("$word")
	fi
done

for s in "${arr2[@]}"
do
	echo -n "$s"
done
echo ''

スクリプトについて

furigana.sh '文章' というふうに使う。

$ furigana.sh '生ビールは美味しい'↩
|生《なま》ビールは|美味《おい》しい

mecab -O wakati で分かち書きし、それを配列に入れて処理を進めている。
 getKanji() という関数は形態素のなかの漢字を取り出すためのもの。最初の引数に処理する形態素、二番目の引数に、その形態素の読みを投じる。
 grep -o '.' とやると、文章をひと文字ずつバラしてくれる。

$ echo 'おどろき' | grep -o '.'↩
お
ど
ろ
き

この「おどろき」を、原文の「驚き」とひと文字ずつ突きあわせて、sed で合致したものを消していく。この場合は「き」が合致するので「驚」だけが残る、はずだ。
 漢字を取り出したら、ふたたび sed で「驚き」から「驚」を消し、「き」だけを残す。これを送り仮名と考える。今度は読みの「おどろき」から「き」を消し、「おどろ」を残す。これが振り仮名だろうと考え、「|驚《おどろ》き」という形に処理する。

ところがこの方法だと、「置き引き」や「女の子」「止まり木」などの、ひとつの形態素に、漢字がひらがなを挟んで複数ある場合はうまくいかない。
「置き引き」から漢字を取り出すと「置引」という、原文にない言葉になってしまい、sed による検索・置換が出来ないのである。
 これについて対処したのがコメントから下の部分である。
 以下のようにすれば、漢字と振り仮名が対応する配列が作れるはずだ。

$ echo '置き引き' | tr 'き' '\n'↩
置
引

$ echo 'おきびき' | tr 'き' '\n'↩
お
び

tr コマンドで、送り仮名にあたる文字を改行にしてみたのである。これで、「置」に「お」を、「引」に「び」という振り仮名を振れる。

このスクリプトの限界

いかにも粗雑で穴のありそうなスクリプトであり、実際にある。以下のような例文はうまくいかない。

$ furigana.sh '仕返ししてやる!'↩
|仕返《かえし》ししてやる!

振り仮名の「し」が送り仮名の「し」で消えてしまうのだ。
 仕返しのほかには、「最も(もっとも)」、「大嫌い(だいきらい)」などが、このパターンに該当する。上に記したような考えかたでは解決できないかもしれない。
 これを「仕返し問題」と命名して、後の自分にたくす。なにか方法を思いついたら修正するかもしれない。

追記(19.03.03)
 ひとつだけ思いついた。getKanji 関数に rev を噛ませるのだ。

function getKanji() {
	local kan=`echo $1 | rev | nkf --hiragana`
	local moji=`echo $2 | nkf --hiragana | grep -o '.'`
	local chara=(`echo $moji`)
	for c in "${chara[@]}"
	do
		kan=`echo $kan | sed -e "s/$c//"`
	done
	echo $kan | rev
}

rev は文字列を逆さまにするコマンドである。

$ echo 'しかえし' | rev↩
しえかし

こうやってから頭の「し」を送り仮名の「し」で消し、ふたたび rev をかければ、「しかえ」が出力される。対症療法的だが、すくなくとも前掲した語句「仕返し」「最も」「大嫌い」は、これで正しく振り仮名を振れる。