初歩のシェルスクリプトで遊ぶ[ぬかみそフォントの制作サポート(14)]
簡易な文字種類を数えるシェルスクリプト『暖簾』
基本処理
『utf8テキスト』『コードリスト』『ビットリスト』
あいお
12354 0 12356 0 12362 0
1 1 0 0 1
いうえお
12356 0 12358 0 12360 0 12362 0
0 1 1 1 1
元のテキストは、普通に読めるutf8のテキストファイルです。このテキストを、unicodeの番号に変換して、1行1文字ずつ、文字の種類をリストにします。処理のために10進数で。
ビットリスト、と名付けたファイルは、見てのとおり「0」「1」の並びです。「1」は文字が有る。「0」は文字が無い。行は、1行目から「あいうえお」を意味してる。
これらの文字データは、それぞれテキストファイルです。
「文字テキスト」→「コードリスト」→「ビットリスト」
「文字テキスト」←「コードリスト」←「ビットリスト」
というように、相互変換ができます。文字をコードに変えて、コードをビットに変えて、ビットをコードに変えて、コードを文字に戻す。
たとえば2つのファイルに共通の文字は何か、を調べたいならば、ビットリストをpasteコマンドで繋げます。
(あ) 10 (い) 11 (う) 01 (え) 01 (お) 11
「あいうえお」は、分かりやすくするための追記です。
行が「11」ならば、両方のテキストに共通して有る文字を意味する。
「10」ならば、左のファイルに有り、右のファイルに無い文字、を意味する。
もし左のファイルが「人名漢字」で、右のファイルが「JIS第一水準」ならば、「11」で検索すると、「人名漢字かつ、第一水準の漢字」が抜き出せます。
pasteで繋げるビットリストファイルは、3つでも同様にできる、のが利点です。たとえば「110」で検索すると、二つに共通し、もう一つに無い文字、なんてのも抜き出せます。
漢字の異体字も扱う
味噌
21619 0 22092 0
0 1 1 0
み噌󠄀
12415 0 22092 917760
1 0 0 1
Code_total
12415 0 21619 0 22092 0 22092 917760
「味噌」と「み噌󠄀」の例です。異体字セレクタがついている「噌󠄀」には、異体字セレクタの数字がコードの2列目に付いてます。
もしさっきのように、ビットの「10」で検索したならば、異体字と基底文字は別の文字として調べることができる。これはこれで必要です。
では、異体字を同じ文字と見なして、共通文字を抜き出すにはどうするか。
今度はビットのファイルをpasteではなくて、コードのファイルをjoinします。
22092 0 917760
共通のキー、つまり基底文字のunicode番号で、一行にまとめて抜き出せました。
この1行を2行に分けて書き出せば、「噌噌󠄀」になります。
もしビットの「1」「0」で共通文字を探していたら、「共通文字は無し」になる。
利点
上に「Code_total」というのがありますが、これは処理中のテキストファイルの全ての文字をコードにしたものです。このtotalを基準にして、ビットリストの「1」「0」を決めています。16進数と文字を書き加えると以下になる。
12415 0 307f 0 み 21619 0 5473 0 味 22092 0 564c 0 噌 22092 917760 564c e0100 噌󠄀
「1」「0」を並べたファイルをpasteでくっつける、というのが最初の思い付きだったんですが、では、行がどの文字を示すかをどうやって決めるか。
最初は異体字を扱わないつもりだったもんで、とりあえず行番号をUnicodeのコード番号にして試作しました。これだと、コードリストが要りません。
TTEditで扱える範囲なら、せいぜい20万行もあれば足ります。基底文字だけなら、こんなのでも十分でした。
しかし、漢字の異体字を扱うには、ちょっと困るわけです。漢字の異体字セレクタはVS17~VS256までもあるし、これらに1行1文字を当てはめるわけにもいかない。
そういう理由から、「〇行目の文字は~である」を決めるファイルを、可変で作る仕様に変えました。
Unicodeの番号を、そのまま行番号とするように、行と文字の対応を固定する方式と比べて優れてるのは、面倒なことを考えなくても済む、って点です。
- 手持ちのテキストファイルの「比較」ができればよいので、Unicodeの最大コード番号は何番だろうか、漢字は何番から何番だろうか、異体字セレクタは何種類使われているだろうか、などと考えなくていい。
- 文字の種類が少ないなら、行数も少なくて済む。速い。
- 「全ての文字の種類のコードリスト」を作るのが面倒そうな気がするが、実はめちゃくちゃ簡単。「cat」でテキストファイルを繋げてしまえばいい。
もともと「文字テキスト→コードリスト」の変換スクリプト自体が、sortコマンドとuniqコマンドを使って、並び替えと重複削除をしています。なので、「これら全てのテキストファイルに使われている文字、すべてのコード」が欲しいのなら、単純にcatで繋げて、一気にスクリプトに突っ込めば済みます。
シェルスクリプトで、文字をコードに変換するのが以下です。
iconvでutf32に変換してから、odでダンプリストに変えてます。異体字セレクタが来たら、直前の文字と並べて出力。
複雑な絵文字とかには対応できませんが、それはそれとして。
#!/bin/sh #set -x # ======================================================================================== # ■ 「暖簾」基底文字とVSを符号化 文字集合のリストを作成する_Unicodeベースで # # ・基底文字と異体字セレクタのリストを作成する。 # # ・異体字セレクタはIVSのみ扱う。 (VS以外は無視) # U+E0100 - U+E01EF (VS17-VS256) # 917760 - 917999 # ・SVSも入れるが、IVSと同様の処理しかしない。 # U+FE00 - U+FE0F (VS1-VS16) # 65024 - 65039 # ・濁点と半濁点 、は検討したけどやめた。 # U+3099 U+309A # # # ▼ 入力形式 # 漢字のIVS異体字を含むutf-8テキスト # stdinから入力する。 # 絵文字は基本的に扱えない。 # # ▼ 出力形式 # codeDec vs # 12378 0 # 12415 0 # 20518 917760 # 20677 0 # 20900 0 # 20900 917760 # 20958 917764 # 21255 917761 # # Unicode10進 # 「0」は基底文字のみ # 「1」以上は基底文字直後に表記された数値のvsが有る # # Unicodeの最大 # 10ffff = 1114111 # # # # # ======================================================================================== # ▼ 入力のダンプリスト化とソート、重複文字データを除去 { # タブと改行は削除する。半角空白は残す。 tr -d '\n\t\r' } | { # utf32leに変換して、1行4バイト、10進数、一文字幅で出力 # 4バイトの値を10進数で直接に出力 iconv -f utf8 -t utf32le | od -tu4 -An -v -w4 #--endian=little } | { # 前コードが0ならば空と見なす preCode='0' while read codeDec ; do if test "${preCode}" -eq 0 ; then # もし前コードが空ならば、基底文字が入ってきているはずなので # いったん基底文字を保存する preCode="${codeDec}" continue elif test "${codeDec}" -ge 917760 && test "${codeDec}" -le 917999 ; then # 前コードが存在し、 # IVSが入ってきたならば # 前コードは基底文字と見なして出力、空(=0)にセット # VSは16進数出力してはならない(sortでVSの順番が狂う) printf '%d\t%d\n' "${preCode}" "${codeDec}" preCode='0' continue elif test "${codeDec}" -ge 65024 && test "${codeDec}" -le 65039 ; then # 同様にSVSが入ってきたならば printf '%d\t%d\n' "${preCode}" "${codeDec}" preCode='0' continue else # 前コードが存在し、 # 基底文字が入ってきたならば、 # 前コードはvs無し基底文字と見なし出力し、 # いったん現基底文字を保存する printf '%d\t%d\n' "${preCode}" '0' preCode="${codeDec}" continue fi done # 最終入力文字は単独基底文字、またはvs # もしvsならば、既に出力されており、preCode=0にセットされている # 単独基底文字ならば、preCodeに保存されている if test "${preCode}" -ne 0 ; then printf '%d\t%d\n' "${preCode}" '0' fi } | { # 10進数でバージョンソートし、重複除去 sort -V | uniq } exit 0