初歩のシェルスクリプトで遊ぶ[CP932テキストファイルを外部コマンドなしでUTF8へ変換する]
初歩のシェルスクリプトで遊ぶ[Unicodeコードポイントをutf8へ変換するシェル関数] - HatenaDiary id:Narr
CP932、いわゆるシフトJISのテキストファイルを、UTF8に変換します。iconvはもちろん、odコマンドも使いません。最後は先週のこれを使います。
いちばん無理矢理なのはodコマンドの代用です。さすがにこれは厳しくて、動かないシェルもあります。バイナリデータはシェルの領分じゃないってことっすね。スクリプトの中にodコマンドでの実装も入れてあります。
CP932とutf8の変換辞書はどうしても必要になりますが、これは単純に変数を大量に定義してます。連想配列的なものを、evalで。このファイルをsourceで読み込むときに外部コマンドを使ってるだけで、他では外部コマンドを使ってない、はず。コマンド置換など、遅くなる要因も、できるだけ減らしてます。
全体としては実用性はありません。しかし、最後の printf \UXXXX の代用関数と、進数変換のシェル関数なんかは、そこそこ便利です。
辞書ファイルの定義を簡単に変えられることが利点といえば利点、か。Unicodeのコードポイントの指定を複数文字にすることができます。たとえば「味噌」を「味噌󠄀」の異体字セレクタがついた状態に直接変換できる。
辞書ファイルは長くてブログに載せられませんでした。そのうちダウンロードのとこに置いておきます。
直接にダウンロードに置くか、chikuwaの中に一緒に置くか、忘れてるか、どれかということで。
初歩のシェルスクリプトで遊ぶ[Unicodeコードポイントをutf8へ変換するシェル関数]
$ printf '\U3042' あ
というようなことがBashではできます。Unicodeのコードポイントを入れると、文字に変換できる、と。
似た機能はzshにはあるみたいですが、高速なdashや、busyboxでは使えませんでした。
今回つくったのは、これの代用シェル関数です。おもにdash,busybox用です。
$ printfEscUoct var 3042 $ printf "${var}" あ
ということをやってます。
外部コマンドを関数内部では使ってません。シェル変数は位置パラメータで代用。コマンド置換、サブシェル、パイプは使っていません。
使用例兼ねて、シェル関数を組み込んだものを掲載します。
テキストデータを標準入力すると、コードポイントと入力した文字を同時に表示するスクリプトです。この文字は何番だろうかな、というのを調べるときに使います。
いったんテキストデータをutf32に変換して、それからutf8に戻すとなると、ループの度に外部コマンドを呼び出すのは非常に重くなります。
#!/bin/sh #!/usr/bin/busybox sh #PATH= #set -x # ============================================================================== # ■ stdinから入力された文字のUnicodeを出力 (busybox sh) # # $ echo 1月2日味噌󠄀 | char2utf32Code.sh # 00000031 00006708 00000032 000065e5 1月2日 # 00005473 0000564c 000e0100 0000000a 味噌󠄀\n # ・入力はutf-8 # ・iconvでUTF-32beに変換することでコードポイントとする。 # ・bash不要 # printf \UHHHHHHHH 代替シェル関数 # ============================================================================== hex2bin(){ # $1 var # $2 hex case "${2:?}" in ( *[!0-9a-fA-F]* ) return 1 ;; esac set -- "${1}" "${2}" '' while test "${#2}" -ge 1 do # 下1桁のみ2進へ変換 case "${2#"${2%?}"}" in ( 0 ) set -- "${1}" "${2%?}" "0000${3}" ;; ( 1 ) set -- "${1}" "${2%?}" "0001${3}" ;; ( 2 ) set -- "${1}" "${2%?}" "0010${3}" ;; ( 3 ) set -- "${1}" "${2%?}" "0011${3}" ;; ( 4 ) set -- "${1}" "${2%?}" "0100${3}" ;; ( 5 ) set -- "${1}" "${2%?}" "0101${3}" ;; ( 6 ) set -- "${1}" "${2%?}" "0110${3}" ;; ( 7 ) set -- "${1}" "${2%?}" "0111${3}" ;; ( 8 ) set -- "${1}" "${2%?}" "1000${3}" ;; ( 9 ) set -- "${1}" "${2%?}" "1001${3}" ;; ( [aA] ) set -- "${1}" "${2%?}" "1010${3}" ;; ( [bB] ) set -- "${1}" "${2%?}" "1011${3}" ;; ( [cC] ) set -- "${1}" "${2%?}" "1100${3}" ;; ( [dD] ) set -- "${1}" "${2%?}" "1101${3}" ;; ( [eE] ) set -- "${1}" "${2%?}" "1110${3}" ;; ( [fF] ) set -- "${1}" "${2%?}" "1111${3}" ;; esac done eval "${1}"'="${3}"' return # 0の明示は禁止 } bin2escOct(){ # 2025-05-03 # bin を \ooo\ooo ...の形式に変換 # $1 var # $2 bin case "${2:?}" in ( *[!0]* ) true ;; ( * ) # 0,00,000,... eval "${1}"'="\\000"' return # return 0 はNG ;; esac case "${2}" in ( *[!01]* ) return 1 ;; esac # var ゼロtrim(bin) set -- "${1}" "${2#"${2%%[!0]*}"}" while test $(( ${#2} % 8 )) -ne 0 do set -- "${1}" "0${2}" done set -- "${1}" "${2}" '' '' "${2}" while test "${#5}" -ge 8 # 8bit x 1 loop do # var 0 8bit \ooo 8bit trim set -- "${1}" "0${2#"${2%????????}"}" '' "${4}" "${5%????????}" while test "${#2}" -ge 3 do case "${2#"${2%???}"}" in ( 000 ) set -- "${1}" "${2%???}" "0${3}" "${4}" "${5}" ;; ( 001 ) set -- "${1}" "${2%???}" "1${3}" "${4}" "${5}" ;; ( 010 ) set -- "${1}" "${2%???}" "2${3}" "${4}" "${5}" ;; ( 011 ) set -- "${1}" "${2%???}" "3${3}" "${4}" "${5}" ;; ( 100 ) set -- "${1}" "${2%???}" "4${3}" "${4}" "${5}" ;; ( 101 ) set -- "${1}" "${2%???}" "5${3}" "${4}" "${5}" ;; ( 110 ) set -- "${1}" "${2%???}" "6${3}" "${4}" "${5}" ;; ( 111 ) set -- "${1}" "${2%???}" "7${3}" "${4}" "${5}" ;; esac done set -- "${1}" "${5}" '' '\'"${3}${4}" "${5}" done eval "${1}"'="${4}"' return # 0の明示は禁止 } UnicodeCP2utf8bin(){ # 2025-05-03 # $1 var # $2 Unicode CodePoint(bin) 8bit-24bit 区切り空白禁止 if eval "${1}"'="null"' then true else return 1 fi case "${2:?}" in ( *[!01]* ) return 1 ;; esac # 1byte # 0000- 0 0000 0000 0000 0000 0000 # 007f 0 0000 0000 0000 0111 1111 # 2byte # 0080- 0 0000 0000 0000 1000 0000 # 07ff 0 0000 0000 0111 1111 1111 # 3byte # 0800- 0 0000 0000 1000 0000 0000 # ffff 0 0000 1111 1111 1111 1111 # 4byte # 10000- 0 0001 0000 0000 0000 0000 # 10ffff 1 0000 1111 1111 1111 1111 # codePoint(bin) -> 21bit set -- "${1}" "00000000000000000000${2}" set -- "${1}" "${2#"${2%?????????????????????}"}" # $@ var , codePoint(21bit) case "${2%????????????????}" in ( 00000 ) set -- "${1}" "${2#"00000"}" # ↓↓ 残り16bit ;; ( * ) # U+010000 - U+10FFFF # $@ var , codePoint(21bit) , codePoint上位5bit set -- "${1}" "${2}" "${2%????????????????}" set -- "${1}" "${2}" "${3#${3%%1*}}" # 0x10 = 00001 0000 if test "${3}" -le 10000 then true else return 1 fi # x10ffff = 1 0000 1111 1111 1111 1111 # 11110(3) 10(6) 10(6) 10(6) # $@ var , 21-1 # var 21-19 18-1 12-1 6-1 set -- "${1}" "${2%??????????????????}" "${2#???}" "${2#?????????}" "${2#???????????????}" # var 21-19 18-13 12-7 6-1 set -- "${1}" "11110${2}" "10${3%????????????}" "10${4%??????}" "10${5}" #printf "${2}${3}${4}${5}" 1>&2 # 【debug】 eval "${1}"'="${2}${3}${4}${5}"' return 0 ;; esac # $@ var , codePoint(16bit) case "${2%???????????}" in ( 00000 ) set -- "${1}" "${2#"00000"}" # ↓↓ 残り11bit ;; ( * ) # U+0800 - U+ffff # $@ var , 16-1 # 1110(4) 10(6) 10(6) # var 16-13 12-1 6-1 set -- "${1}" "${2%????????????}" "${2#????}" "${2#??????????}" # var 16-13 12-7 6-1 set -- "${1}" "1110${2}" "10${3%??????}" "10${4}" #printf "${2}${3}${4}" # 【debug】 eval "${1}"'="${2}${3}${4}"' return 0 ;; esac # $@ var , codePoint(11bit) # 0x7f = 000 0111 1111 # 0x80 = 000 1000 0000 case "${2%???????}" in ( 0000 ) # U+0000 - U+007f set -- "${1}" "${2#"0000"}" eval "${1}"'="${2}"' return 0 ;; ( * ) # U+0080 - U+07ff # 110(5) 10(6) # 0x07ff= 00000111 11111111 # $@ var , 11-1 # var 11-7 6-1 set -- "${1}" "110${2%??????}" "10${2#?????}" #printf "${2}${3}" # 【debug】 eval "${1}"'="${2}${3}"' return 0 ;; esac return 2 } printfEscUoct(){ if eval 'hex2bin '"${1}"' "${2}"' then true else return 1 fi if eval 'UnicodeCP2utf8bin '"${1}"' "${'"${1}"'}"' then true else return 1 fi if eval 'bin2escOct '"${1}"' "${'"${1}"'}"' then return 0 else return 1 fi } printf_bsU(){ # printf \U # 改行文字を除く case "${1}${2}${3}${4}" in ( '0000000a' ) printf '\\n' ;; ( * ) printfEscUoct var "${1}${2}${3}${4}" printf "${var}" #printf '\U'"${1}${2}${3}${4}" # bash ;; esac } iconv -f utf8 -t utf32be -- - | od -tx1 -An -v -w16 | { while read -r x01 x02 x03 x04 x05 x06 x07 x08 \ x09 x10 x11 x12 x13 x14 x15 x16 do printf '%s' "${x01}${x02}${x03}${x04} " printf '%s' "${x05:-" "}${x06:-" "}${x07:-" "}${x08:-" "} " printf '%s' "${x09:-" "}${x10:-" "}${x11:-" "}${x12:-" "} " printf '%s\t' "${x13:-" "}${x14:-" "}${x15:-" "}${x16:-" "}" printf_bsU "${x01}" "${x02}" "${x03}" "${x04}" printf_bsU "${x05:-00}" "${x06:-00}" "${x07:-00}" "${x08:-20}" printf_bsU "${x09:-00}" "${x10:-00}" "${x11:-00}" "${x12:-20}" printf_bsU "${x13:-00}" "${x14:-00}" "${x15:-00}" "${x16:-20}" echo done }
初歩のシェルスクリプトで遊ぶ[16進ダンプリストをバイナリに変換]
#!/bin/sh #!/usr/bin/busybox sh #PATH='' #set -x # ========================================================================================= # ■ 16進数の入力をバイナリに変換する # # xxd -r -pの代替品 # # 入力は16進数の偶数桁 空白またはタブ区切り # input:stdin # output:stdout # # (OK) # 12 34 5678 90aB cdef # 6666 ffff Ff # # (NG) # 123 456 789 0ab cde # 1 2 3 4 # # # 2025-03-31 # ========================================================================================= while read Line do # 不要文字があれば終了 case "${Line}" in ( *[!0-9a-fA-F' ']* ) echo "${Line}" echo "不正な入力文字があります" exit 1 ;; ( * ) true ;; esac 1>&2 # 先頭から2文字ずつhex->oct変換しつつstdout # Lineの行頭にIFSはない ∵行頭,行末に[ ]があれば既にreadが削っているため while test "${#Line}" -ge 1 do # 数値が先頭に2文字あれば # 先頭2文字のみを8進変換してstdout case "${Line%"${Line#??}"}" in ( [0-9a-fA-F][0-9a-fA-F]* ) printf '%03o\n' 0x"${Line%"${Line#??}"}" ;; ( * ) echo "[${Line}] :区切りが不自然です" 1>&2 exit 1 ;; esac # 先頭2文字を削除 Line="${Line#??}" # 行頭の連続空白とタブを削除 Line="${Line#"${Line%%[! ]*}"}" done done | while read Oct do printf '\'${Oct} done
ダンプリストからはデータ以外のもの、アドレスやらは削る必要があります。
空っぽに見える変なところは、たぶん半角空白とタブ文字が入ってます。
debianのdash,busybox(win)用です。