初歩のシェルスクリプトで遊ぶ[シフトJISをutf8に変換-2]

調べものには使えません。

なりゆき

初心者知識と行き当たりばったりで、軽く遊ぼうと思った。フォントを作るのに文字コード見てたのに、規格やらは何も知らんので、これを機会に少し知っておくのもいいだろうと。

↓最初に組んだのの出力がこれで、改行を無視してます。

0030 0031 0032 0033 
0009 0009 0074 0061 0062 
0020 0020 0020 0020 0020 0020 0020 0020 0020 0020 0073 0070 
9046 82cd 93f5 82d6 82c7 0020 8e55 82e8 82ca 82e9 82f0 
89e4 0020 82aa 0020 90a2 0020 924e 0020 82bc 0020 8fed 0020 82c8 0020 82e7 0020 82de 
974c 88d7 82cc 899c 8e52 0020 0020 8da1 93fa 897a 82a6 82c4 
90f3 82ab 96b2 8ca9 82b6 8140 8140 908c 82d0 82e0 82b9 82b8 

この数字を、端末で読めるutf8の文字と入れ替えると、文章は読めた。
てっきり文字が読めるところまで行けないかと思ってたので、すぐ読めてびっくりした。いや、TTEdit文字コード表を見ただけなもんで……。
文字への変換は、とりあえずはgrepで済ませてました。
テキストファイル、7800行のやつを作って、一行ずつ文字コードと文字をタブ区切りにして、grep

82988299	y
829a	z
829f	ぁ
82a0	あ

図書カード:ガリバー旅行記
テストに使ってたのは、青空文庫ガリバー旅行記です。
ガリバー旅行記一冊分で、4分近くかかりました。けど、とにかくできた。

面白いうちは続けよう

この後、改行「0a」も含めて数字の列で出力することにして。じゃあ、自分にどこまでできるか。
この時点でxxdコマンドのこととか、知りません。utf8の中身もわかりません。数字を文字と入れ替えるしかないかな、と思ってた。

文字コードを文字に変換する方法が決まらない。結局比較的マシだったのは、やっぱり丸暗記。

SJIS8298=''
SJIS8299=''
SJIS829a=''
SJIS829f=''
SJIS82a0=''
SJIS82a1=''

ガリバー、2秒で終わった。
しかしこれ、使う文字も使わない文字も、問答無用全部って、嫌だよこんなの。
一文字ずつディスクを読むよりは、機械に優しい気もするけど、でもねぇ。

7884ファイル

#!/bin/dash

: << '#--------------------------------------------------------------comment'
shiftJISテキストファイルを数値に変換したデータを受け取って、
辞書テキストファイルから文字を拾って、テキストファイルを復元する

--辞書形式:細胞辞書形式
$ time cat gulliver_ryokoki_0d0aCode.txt |./sjisCodeToChar16.sh >/dev/null
real	0m7.665s
user	0m5.994s
sys	0m1.503s
#--------------------------------------------------------------comment

dict="./dic/nukamisoCP932Dic_06_cell/"
#ls ${dict}*  && exit

sjisCodeToChar16(){
(
cd "${dict}"
while read line ;do
  cat ${line}
done ;
)
}

: << '#--------------------------------------------------------------test'
echo "0030 0031 0032 0033 0d0a 0009 0009 0074 0061 0062 0d0a 0020 0020 0020 0020 0020 
0020 0020 0020 0020 0020 0073 0070 0d0a 9046 82cd 93f5 82d6 82c7 0020 8e55 82e8 
82ca 82e9 82f0 0d0a 89e4 0020 82aa 0020 90a2 0020 924e 0020 82bc 0020 8fed 0020 
82c8 0020 82e7 0020 82de 0d0a 974c 88d7 82cc 899c 8e52 0020 0020 8da1 93fa 897a 
82a6 82c4 0d0a 90f3 82ab 96b2 8ca9 82b6 8140 8140 908c 82d0 82e0 82b9 82b8 " |
#--------------------------------------------------------------test
sjisCodeToChar16

exit 0


↑入力は、16進数の数字の列が、ひとつ1文字。これを一行ずつ読み、catにそのまま渡す。すると、数字に対応した文字の入った小さなファイルが、カレントディレクトリから読み込まれて、テキストファイルの切れ端になって、標準出力から飛んでゆく。1ファイル1文字のテキストファイルになってるわけです。
こんな頭の悪い方法でも、ガリバー旅行記が8秒で組めるし、結果として出来たファイルは、md5を取ってもちゃんとutf8のテキストファイルになりました。テキストエディタで変換したのと同じデータを作れる。


次は、この膨大な細かいファイルを、どうやって減らすか。
つづく。

スクリプト

辞書ファイルを作らないと、テキストファイルは作れません。
とりあえず載せときますが、試す価値は無いと思う。

cp932テキストファイル→cp932の文字コードを1文字ずつ分けたもの
#!/bin/sh
#emulate -R sh 2>/dev/null
#set -o SH_WORD_SPLIT
: << '#--------------------------------------------------------------comment'
odコマンドで,CP932テキストファイルを数値に変換
改行文字[0d0a]、タブ[0009]に対応
16文字分で改行表示

----odコマンドのオプション
アドレス表示なし、16進数1バイト、連続数値の省略[*]なし、幅64バイト
echo "xxxxxxxx"| od -A n -t x1 -v -w1
$ echo "あお" | od --endian=big --address-radix=n --format x1 --output-duplicates --width=64 |
 e3 81 82 e3 81 8a 0a
----od以外のコマンドで
...  | hexdump -v -e '16/1 " %02x"' -e '"\n"'
...  | xxd -i -c 16 |sed 's/0x//g;s/,//g'
高速な順にhexdump,od,xxd+sed。十分に早いから、どれでも使える。
----出力形式
$ cat _iroha-sjis.txt | ./sjisTextToSjisCode21.sh 
0030 0031 0032 0033 0d0a 0d0a 0009 0061 0062 0063 0064 0065 0066 0d0a 0d0a 9046 
82cd 93f5 82d6 82c7 0020 8e55 82e8 82ca 82e9 82f0 0d0a 89e4 0020 82aa 0020 90a2 
0020 924e 0020 82bc 0020 8fed 0020 82c8 0020 82e7 0020 82de 0d0a 974c 88d7 82cc 
899c 8e52 0020 0020 8da1 93fa 897a 82a6 82c4 0d0a 90f3 82ab 96b2 8ca9 82b6 8140 
8140 908c 82d0 82e0 82b9 82b8 

----処理概略
・odの出力を、1行64バイト程度に整形して、while-read で行単位で読み込み、forループに処理を渡す。
二重ループだが、while-readで一文字ずつ処理するより高速になる。

・echoの出力は1行16文字分ずつ、できる
forループが一周して終わっても、ループ内の変数は消えずに、次のループに引き継がれて処理される。
2バイト文字が1バイトずつ切れても、問題なく処理される。

・最終行の出力
whileループ同様に16文字まとまらないとechoできないが、${byteLine}に文字列が残っているのも同様。
別プロセスにまとめる()は必要。


・ガリバー旅行記
https://www.aozora.gr.jp/cards/000912/card4673.html
$ time cat gulliver_ryokoki.txt | ./sjisTextToSjisCode21.sh >/dev/null 
real	0m1.843s
user	0m1.364s
sys	0m0.468s


・#!/bin/bash でなく #!/bin/sh (dash)で
sh(dash)が使えるなら、bashに比べて5倍の高速化ができる。

・zshのforループでは単語分割されない
変数を展開後、空白で単語分割されない仕様のzshでは、forループが動作しない。対策はあるが、一長一短。
emulate -R sh 2>/dev/null
……エラーを捨てればsh,bashともに、問題なく動く。
set -o SH_WORD_SPLIT
……sh,bashはエラーで停止
eval 'for in'${line}'do; done'
sh,bash、ともにエラーもなく正常動作する。しかしシングルクォートする範囲が広すぎる。途中で切ることもできない。

#--------------------------------------------------------------comment

sjisTextToSjisCode21(){
doubleByte="FALSE"			# 1バイト目は、2バイト文字の下位バイトではない


od --endian=big --address-radix=n --format x1 --output-duplicates --width=64 |
#hexdump -v -e '16/1 " %02x"' -e '"\n"' |
#xxd -i -c 16 |sed 's/0x//g;s/,//g' |
( while read line ;do		# () でwhile を括っておく

for byte in ${line} ;do	# ${line}を "" で括ってはいけない

if test "${doubleByte}" = "JP" ;then	#2バイト文字上位の直後なら、下位バイトと確定
byteLine="${byteLine}${byte} "			# 「@@ 」後ろに空白
doubleByte="FALSE"					# 2バイト判定をリセット
CharaCount="${CharaCount}@"				# 文字数インクリメント

else	# もし直前バイトが文字の切れ目だったならば
	case ${byte} in
	( [234567abcd]? | 09 )				# 1バイト文字、またはタブ文字と確定
	byteLine="${byteLine}00${byte} " 	# 「00@@ 」
	CharaCount="${CharaCount}@"			# 文字数インクリメント
	;;
	( [89ef]? | 0d )					# 2バイト文字上位と確定
	byteLine="${byteLine}${byte}"		# 「@@」 続く下位バイトのため、空白は空けない
	doubleByte="JP"						# 2バイト文字判定をセットし、次ループを下位バイトとする
	;;
	esac
fi

if test "0${CharaCount}" = "0@@@@@@@@@@@@@@@@";then	# もし文字数が@@...@@ならば
echo "${byteLine}"										# 出力して改行
byteLine="" ;CharaCount=""								# 文字列Line,文字数Countをリセット
fi
done
done
echo "${byteLine}" ) # 未出力の${byteLine}を拾う。必ず () の中で
}

sjisTextToSjisCode21 #| tee temp_sjisTextToSjisCode21_${$}.txt 

exit 0
cp932の16進数→読める文字
#!/bin/sh

: << '#--------------------------------------------------------------comment'

shiftJISテキストファイルを数値に変換したデータを受け取って、
辞書テキストファイルから文字を拾って、テキストファイルを復元する


--辞書形式--- 辞書をそのまま変数に代入する。文字コードの頭にSJISをつける
SJIS00de='゙'
SJIS00df='゚'
SJIS0d0a='
'
SJIS8140=' '
SJIS8141='、'
SJIS8142='。'

$ time cat gulliver_ryokoki_0d0aCode.txt |./sjisCodeToChar13.sh >/dev/null
real	0m1.358s
user	0m0.827s
sys	0m0.525s
#--------------------------------------------------------------comment


sjisCodeToChar13(){
dict="./dic/nukamisoCP932Dic_04.txt"
#cat ${dict} | head && exit

#辞書を変数にすべて格納する
. "${dict}"


tr " " "\n" |
while read byte ;do
	#文字コードを文字に入れ替えてstdout 改行もできる
	eval 'echo -n "${SJIS'${byte}'}"'

#改行のecho 改行なしの入力の場合は最終文字のecho
#echo ""
done
}

: << '#--------------------------------------------------------------test'
echo "0030 0031 0032 0033 0d0a 0d0a 0009 0061 0062 0063 0064 0065 0066 0d0a 0d0a 9046 
82cd 93f5 82d6 82c7 0020 8e55 82e8 82ca 82e9 82f0 0d0a 89e4 0020 82aa 0020 90a2 
0020 924e 0020 82bc 0020 8fed 0020 82c8 0020 82e7 0020 82de 0d0a 974c 88d7 82cc 
899c 8e52 0020 0020 8da1 93fa 897a 82a6 82c4 0d0a 90f3 82ab 96b2 8ca9 82b6 8140 
8140 908c 82d0 82e0 82b9 82b8 " |
#--------------------------------------------------------------test
sjisCodeToChar13



exit 0