查找中文 Unicode 編碼下的隱藏錯 (別) 字!



前言

  • 記得以前在調整「期貨交易網路文章推薦」這篇的格式與贅字,發現有不少重複的字,這可能是由於 pdf 或各種原始來源轉檔後的問題。
  • 近期發現這些贅字有漏網之魚,之前沒有修正到,簡單紀錄查找與取代的過程。



說明

發現編碼不同

每隔一小段時間我就會重讀一次這些網路文章,剛好看到之前沒改到的漏網之魚:

vscode 中用 ⌘ + F 搜尋 ,結果根本沒搜到重複的兩個字。

特別貼了左邊的「」,

結果一細查發現左邊的字是部首、右邊的字是一般文字!差別在於 unicode 編碼不同;例如:

  • 「⽤」U+2f64、「用」U+7528
  • 「⼀」U+2f00、「一」U+4e00

這是這次修改的部分內容:

全部改成 「」 (U+7528) 就沒問題了。


編碼調查指令

再進一步查找之前,我們可以紀錄一個指令,方便我們在 terminal 對照編碼 (source [4]) :

純 bash

1
2
3
4
5
6
hex() {
  printf '%x\n' "'$1"
}

hex "用"
# 7528

Note

  1. 注意 "'$1"裡面的單引號 ' 只有一個,這是告訴 bash 把後面的文字照字面上的意思傳入 printf 中。

或者 python 版

1
2
3
4
5
6
hex() {                                                                                                                            1 
   python3 -c "print(hex(ord('$1')))"
}

hex "用"
# 0x7528

一口氣查找

根據 wiki [1] 中一路查到兩個 pdf 文件 [2][3],於是在 vscode 中用 regex (正規表達式) 一次查找所有的部首:

[\u2E80-\u2EFF]   # CJK Radicals Supplement
[\u2F00-\u2FDF]   # Kangxi Radicals

一搜下去,發現還有大約 20 個字要修改!

像是

1
2
3
4
5
hex ⻄
2ec4

hex 西
897f

如果你只有找到幾個字,一個個找到編碼用 regex 字串取代就可以了。

例如說上面的「西」(U+2ec4) 要換成「西」(U+897f),

但是既然我有 20 個字要改,那就只好批次處理了!

起手式 grep

先找 2f 開頭的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
file_path="<該篇文章路徑>"

for word in $(grep -oP '[\x{2f00}-\x{2fdf}]' $file_path) ; do 
   printf '%s  %s\n' $word  $(hex ${word})
done | sort -u

# 結果
⼆  2f06
⼈  2f08
⼝  2f1d
⼾  2f3e
⽀  2f40
⽇  2f47
⽉  2f49
⽑  2f51
⽗  2f57
⽣  2f63
⽩  2f69
⽯  2f6f
⽽  2f7d
⾨  2fa8
⾼  2fbc
⾾  2fbe

還有 2e 開頭

1
2
3
4
5
6
7
8
file_path="<該篇文章路徑>"

for word in $(ggrep -oP '[\x{2e80}-\x{2eff}]' ${file_path}) ; do 
   printf '%s  %s\n' $word  $(hex ${word})
done | sort -u

# 結果
⺠  2ea0

Note

  1. 因為我們用 grep -P 來使用 Perl 兼容的正規表達式,他不支援 \u 的寫法,所以要改成使用 \x{????} 而不是 \u????
  2. 根據前面提到的這兩個 pdf 文件 [2][3],只要是 2f 或是 2e 開頭的,就代表他是部首,而不是普通的字。

一口氣取代

把整串結果貼到正規化工具中,例如我找到這個 [5],從 Unicode NFKC 選項中可以拿到轉換後的文字。

現在我們有:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 示意圖
# before             # after
⼆    2f06    ->     二    4e8c
⼈    2f08    ->     人    4eba
⼝    2f1d    ->     口    53e3
⼾    2f3e    ->     戶    6236
⽀    2f40    ->     支    652f
⽇    2f47    ->     日    65e5
⽉    2f49    ->     月    6708
⽑    2f51    ->     毛    6bdb
⽗    2f57    ->     父    7236
⽣    2f63    ->     生    751f
⽩    2f69    ->     白    767d
⽯    2f6f    ->     石    77f3
⽽    2f7d    ->     而    800c
⾨    2fa8    ->     門    9580
⾼    2fbc    ->     高    9ad8
⾾    2fbe    ->     鬥    9b25


# 實際的值

before="⼆
⾾"

after="二
鬥"

然後可以用一個 loop 重複 call sed 來取代全部的字元:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
file_path="<該篇文章路徑>"
before_arr=($(echo "$before"))
after_arr=($(echo "$after"))

len=${#before_arr[@]}

# 想要的話可以 double check
for (( i=1; i<=$len; i++ )) ; do
   printf '%s  %s    ->    %s  %s\n' ${before_arr[$i]} $(hex ${before_arr[$i]}) ${after_arr[$i]} $(hex ${after_arr[$i]})
done


# main loop
for (( i=1; i<=$len; i++ )) ; do
   sed -i '' 's/'${before_arr[$i]}'/'${after_arr[$i]}'/g' $file_path
done

取代的概念會像是這樣:

1
2
3
sed -i '' 's/⼆/二/g' <該篇文章路徑>
#            │  └─ 4e8c
#            └─ 2f06

大功告成!

也許你會想,如果都能直接從線上工具取得正規化 (normalization) 後的字串,那我把整篇文章貼上去不就好了?

技術上可以,我沒有這樣做的原因是

  • 他會把我習慣的全形標點符號全部轉換成半形
  • 順便練習寫個簡單的 bash script

如果你可以接受「標點符號先被轉換過、自己再去處理一輪標點符號」的話,也可以不用 script 處理。


補充

文章內的字元

如果你在這篇文章中直接搜尋對應的文字,例如「」,會匹配所有的結果,這是因為預設情況下為了避免麻煩,本站的打包工具已經自動正規化 (normalization)。

例如在第一節中,最後一段我的原始文字是這樣的:

1
2
3
結果一細查發現左邊的字是部首、右邊的字是一般文字!差別在於 unicode 編碼不同;例如:
   -&#x2F64;`U+2f64`、「&#x7528;`U+7528`   -&#x2F00;`U+2f00`、「&#x4E00;`U+4e00`

在打包之後,工具已經將所有常見的部首轉換成一般文字;

技術上來說是

  • 將「中日韓部首」(CJK Radicals) ,包含康熙部首 (Kangxi radicals)、以及「中日韓部首補充」(CJK Radicals Supplement)
  • 轉為「中日韓統一表意文字」(CJK Unified Ideograph)

Unicode 前綴

目前註記 Unicode 字元已經出現三種常見寫法,

在不同情境中會有不同的用途與顯示方式:

前綴 範例 用途
U+ U+7528 註記是 Unicode 字元,給人類閱讀用,常見於 Unicode 文件或討論中
\u \u7528 程式中用來表示 Unicode 字元,常見於 regex、JavaScript、JSON 等
0x 0x7528 用 16 進位表示的整數(code point 整數值),常用於底層程式或資料格式中



REF

  1. https://en.wikipedia.org/wiki/Kangxi_radicals#In_Unicode
  2. https://www.unicode.org/charts/PDF/U2F00.pdf
  3. https://www.unicode.org/charts/PDF/U2E80.pdf
  4. https://stackoverflow.com/a/18569033
  5. https://dencode.com/string/unicode-normalization

主題 StackJimmy 設計