駄文

水伝ブックマークを休止して2年ですが、以来、水伝や疑似科学方面のことは調べてません。
少し書き散らかしたくなったので、ダラダラ垂れ流します。
意味わからなくてもいいし、分かった気分になってもコメント要りません。

続きを読む

初歩のシェルスクリプトで遊ぶ[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}"
  1. Unicodeコードポイントの16進数を、2進数に変換
  2. 2進数を弄って、utf8の2進数に変換
  3. 2進数を「printf '\ooo\ooo'」の形式の8進数に変換

ということをやってます。
外部コマンドを関数内部では使ってません。シェル変数は位置パラメータで代用。コマンド置換、サブシェル、パイプは使っていません。

使用例兼ねて、シェル関数を組み込んだものを掲載します。
テキストデータを標準入力すると、コードポイントと入力した文字を同時に表示するスクリプトです。この文字は何番だろうかな、というのを調べるときに使います。
いったんテキストデータを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)用です。