初歩のシェルスクリプトで遊ぶ[ぬかみそフォントの制作サポート(15)]

文字種類管理シェルスクリプトで、文字コードのリストを「1」「0」のリストに変換する

簡易な文字種類を数えるシェルスクリプト『暖簾』について。
昨日は普通のテキストファイルを文字コードのリストに変換するスクリプトについて書いた。今日は文字コードのリストから、「1」「0」のリストに書き換えるスクリプトを組みます。


味噌
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
12415	0	307f	021619	0	5473	022092	0	564c	022092	917760	564c	e0100	噌󠄀

「味噌」「み噌󠄀」の例です。

すべての文字を含んだ「Code_total」を基準にして、もしテキストファイルに文字が含まれれば、その行を「1」に。含まれないなら「0」にします。
たとえば、while readでループ処理するなら、

  1. 「味噌󠄀」または「み噌󠄀」から作ったコードリストファイルを、シェル変数配列(たとえば位置パラメータにsetする)に入れる。
  2. while readループに、「Code_total」を標準入力する。入力1行につき、出力に「1」または「0」を出す。
  3. もしCode_totalの文字コードが、シェル配列変数の1番(最も小さい数字)と同じならば、「1」を出力。配列をshiftする。次の文字コードを1番にずらす。
  4. もしCode_totalが配列1番より小さいなら、「0」を出力。配列はそのまま弄らない。
  5. 次のループへ

joinとrev

コメント多いですが、スクリプトは短いです。

#!/bin/sh
#set -x
# =========================================================================================
# ■ 「暖簾」ビットリスト作成 基準文字ファイルと比較してビットリストファイルを作成する
# 
# 
# stdin 解析対象テキストのコードリストファイル。
# ${1}  行番号とunicodeの対応の基準となるコードリストファイルで、
#       解析対象テキストの文字をすべて含む。
# 
# どちらも、このスクリプトで使うには、ソートしておく必要はない。
# 
# 
# ▼ 処理概略
# 
# ・入力受け取りと加工
# 
#      stdin       --> tr \t _  空けて末尾に1 -> 一時ファイルに記録 
# [基底数字 VS数字] -> [%07基底数字%07VS数字 \t 1] -----> ${targetCode} 
# 
#      ${1}            tr \t _  空けて末尾に0
# [基底数字 VS数字] -> [%07基底数字%07VS数字 \t 0]
# 
# ・joinを実行
#    結合key 「基底文字_VS数字」
#    -a 1      基準コードリストを残す。
# 
# 17418_0	0
# 17419_0	0	1
# 17419_917760	0	1
# 17421_0	0
# 17422_0	0	1
# 90_0	0	1
# 
# 全ての行には2列目に「0」がある。
# もしstdinの「基底文字+VS数字」に共通キーがあれば、3列目に「1」が付加される。
# 行末の数字が「1」ならば、stdinに文字が存在した。
# 同様に「0」ならば、文字は存在しなかった。
# 何らかのコマンドで以上を判定し、「0」「1」を出力する。
# 
# ▼ 「_」で文字列結合する理由
#  ・入力時はsortが自然数かバージョンのため、joinできない。
#  ・基底文字と異体字セレクタの両方をキーにしなければならない。
#  ・ゼロ埋めするとsortを回避できるが、桁数を間違えるとまずい。
#  ・「_」ならtrで簡単にタブに戻せる。
#  ・sortの-k指定があれば、末尾の「0」「1」を無視して自然数ソートができる、はず。
# 
# =========================================================================================

# standard
# 90	0
# 17418	0
# 17419	0
# 17419	917760
# 17421	0
# 17422	0

# target
# 90	0
# 17419	0
# 17419	917760
# 17422	0

joinPrepSh(){ (
cut -f 1-2 | tr '\t' '_' | sort |
sed -e "s/$/	${1}/"
) }


  if ! file "${1}" ; then exit 1
elif test "`file -b -i "${1}" `" != 'text/plain; charset=us-ascii' ; then exit 1
else
  standardCode="${1}"
fi 1>&2


# stdin受け取り
targetCode=`mktemp`
joinPrepSh '1' 1> "${targetCode}"

if test -z "`cat -- "${targetCode}"`"
then exit 1
fi 1>&2

#cat -- "${targetCode}" && exit #【debug】
# 17419_0	1
# 17419_917760	1
# 17422_0	1
# 90_0	1

#joinPrepSh '0' 0< "${standardCode}" && exit #【debug】
# 17418_0	0
# 17419_0	0
# 17419_917760	0
# 17421_0	0
# 17422_0	0
# 90_0	0


# 結合できなかったtargetCodeを抽出する
# targetCodeが全て基準コードに含まれているならば、無出力で単語数は0
# 新しい文字が混じっているならば、単語数は2以上

if test `joinPrepSh '0' 0< "${standardCode}" |
         join -v 2  --  - "${targetCode}" | wc -w ` -eq 0 ; then
  true
else
  echo '解析対象に未登録の文字が存在します。' 1>&2
  rm -- "${targetCode}"
  exit 1
fi



# ---- join ------------
# 17418_0	0
# 17419_0	0	1
# 17419_917760	0	1
# 17421_0	0
# 17422_0	0	1
# 90_0	0	1

# ---- sort ------------
# 90	0	0	1
# 17418	0	0
# 17419	0	0	1
# 17419	917760	0	1
# 17421	0	0
# 17422	0	0	1


# joinをタブ区切りにしておくこと
HT="`printf '\t'`"
joinPrepSh '0' 0< "${standardCode}" | join -a 1 -t "${HT}" -- - "${targetCode}" |
  tr '_' '\t' | sort -k 1n,1 -k 2n,2 | rev | cut -f 1



rm -- "${targetCode}"
exit 0
  1. コード番号リストの、水平タブをアンダースコアに入れ替える。「基底文字_異体字セレクタ」に、繋げる。
  2. sort
  3. さらに水平タブ区切りを入れてから、行の末尾に「0」または「1」を追記する。基準文字コードリストには「0」を。解析対象のコードリストには「1」を。
  4. ふたつの加工したコードリストを、joinコマンドで繋げる。「-a」オプションで基準文字コードリスト側を残す。
  5. 解析対象のコードリストには存在しない文字の行は、行末が「(タブ)0」になる。
  6. 解析対象に存在するコードの行は、行末は「(タブ)0(タブ)1」になる。
  7. 付加したアンダースコアを除去し、基底文字と異体字セレクタの列で自然数ソート。
  8. 行の末尾の「0」「1」のみを抜き出すと、「0」「1」のみの、ビットのリストになる。抜き出しにはrevとcutを使う。


というようにすると、シェルでのループ操作や条件判定を書かなくても、既存のコマンドの組み合わせで処理できます。

$ cat ./Code_total.txt 
12415	0
21619	0
22092	0
22092	917760

$ cat ./miso.txt 
12415	0
22092	917760

$ sed 's/\t/_/;s/$/\t0/' 0< Code_total.txt | join -a 1 -- - <( sed 's/\t/_/;s/$/\t1/' 0< miso.txt ) | rev | cut -d ' ' -f 1
1
0
0
1