Blankego's Humble Home
Tags: ime awk

輸入法開發利器 - awk篇

做輸入法就意味着要成天跟碼表打交道。可是碼表並不是一個好擺弄的東東。工欲善其事,必先利其器,從今天起,我將開一個利器系列的主題,向大家介紹一些最實用、最簡單、最稱手的文本處理工具,以助我碼人一臂之力。

閑言少叙,AWK是個啥意思、甚麼背景、咋個來歷等等等等,我們一概不表,直奔主題,直接拿表碼處理的例子說事。

例一 字詞分離

如下面這一種碼表(CharWordMixedTable.txt),詞字混雜在一起,問:我想要從中提取出字表來,該怎樣辦到? 复制内容到剪贴板

...
州際|10|ㄓㄡ ㄐㄧˋ
州際公路|1|ㄓㄡ ㄐㄧˋ ㄍㄨㄥ ㄌㄨˋ
巟|11|ㄏㄨㄤ
巠|100|ㄐㄧㄥ
巡|1072|ㄒㄩㄣˊ
巡佐|0|ㄒㄩㄣˊ ㄗㄨㄛˇ
巡哨|3|ㄒㄩㄣˊ ㄕㄠˋ
巡回|9|ㄒㄩㄣˊ ㄏㄨㄟˊ
...

【答案】

复制内容到剪贴板

awk -F \| '{if(length($1)==1)print}' CharWordMixedTable.txt > CharTable.txt

所得的CharTable.txt為: 复制内容到剪贴板

...
巟|11|ㄏㄨㄤ
巠|100|ㄐㄧㄥ
巡|1072|ㄒㄩㄣˊ
...

【講解】


州里 ㄓㄡ ㄌㄧˇ
州長 ㄓㄡ ㄓㄤˇ
州際 ㄓㄡ ㄐㄧˋ
州際公路 ㄓㄡ ㄐㄧˋ ㄍㄨㄥ ㄌㄨˋ
巟 ㄏㄨㄤ
巠 ㄐㄧㄥ
巡 ㄒㄩㄣˊ
巡佐 ㄒㄩㄣˊ ㄗㄨㄛˇ
巡哨 ㄒㄩㄣˊ ㄕㄠˋ
巡回 ㄒㄩㄣˊ ㄏㄨㄟˊ
巡回醫療 ㄒㄩㄣˊ ㄏㄨㄟˊ ㄧ ㄌㄧㄠˊ
巡夜 ㄒㄩㄣˊ ㄧㄝˋ
巡官 ㄒㄩㄣˊ ㄍㄨㄢ
巡察 ㄒㄩㄣˊ ㄔㄚˊ
巡察隊 ㄒㄩㄣˊ ㄔㄚˊ ㄉㄨㄟˋ

例二 拆分多項字段

如下面這個碼表(code2char.txt),左邊字段(列)是編碼,右邊是編碼所對應的字。有的編碼會對應多個漢字,即所謂重碼了。我想由此表導出一個漢字對編碼的表,要求每行的漢字字段只有一個漢字,每個漢字右邊都給出相應的編碼(見【結果】)。問:該如何導出此表? 复制内容到剪贴板

eeyz 鞃
eez 藄 葚
eezf 鞢 葉
eezg 鞵
eezq 蘛
eezu 韀 韉 韅
eezy 靿
efa 苯
efae 蓒
efb 荰 莆
efbb 蓕
efbk 蔈
efbz 蕓
efd 莤
efds 蓴 蒪

【結果】

鞃 eeyz
藄 eez
葚 eez
鞢 eezf
葉 eezf
鞵 eezg
蘛 eezq
韀 eezu
韉 eezu
韅 eezu
靿 eezy
苯 efa
蓒 efae
荰 efb
莆 efb
蓕 efbb
蔈 efbk
蕓 efbz
莤 efd
蓴 efds
蒪 efds

【答案】

awk '{for(f=2;f<=NF;f++)print $f " " $1}' code2char.txt > char2code.txt

【講解】

for( ;; )也是跟C的for語句功能、句法相同。NF=number of fields(字段數)。比如上表的「eezu 韀 韉 韅」行,AWK自動將其拆分成$1 $2 $3 $4四列,並把字段數4賦予變量NF。對該行而言,for(f=2;f<=NF;f++)...意為:設f為2,只要f小於等於NF(4),就執行指令「...」,然後再設ff+1。重複該語句,直到 f 大於 NF(4)為止。$f 會被替換成 $2(第一次執行)、 $3 (第二次執行)...那麼該語句就等價於:

print $2 " " $1    # 輸出 「韀 eezu」
print $3 " " $1    # 輸出 「韉 eezu」   
print $4 " " $1    # 輸出 「韅 eezu」

關於操作系統

本文所介紹的awk和以後將要介紹的其他工具都是以linux為原始運行環境。想在win上使用,請先安裝cygwin。Cygwin 可以模擬linux的shell環境,且對utf8文本支持不錯,比win xp自帶的cmd強得多。比如本文例一中的length($1),因為uft8是不定長編碼(每個漢字3-4字節),在cmd 上恐怕得不到相同的結果,而在cygwin上就沒問題。

注意:截圖中第一列有若干字顯示為空白。它們是gbk字集以外的字,因命令行font fallback 的功能有缺陷,所以顯示不出。如果將結果輸出到文本文件:「指令 原文件 > 目標文件」,把目標文件用支持大字集的文本編輯器打開(當然得先安裝大字集字型檔),就能看到完整的結果了。

PS:安裝 cygwin 請選擇 mirrors.ustc.edu.cn 作為下載站點,速度較快。 PPS:剛剛試了一下,cygwin也不行,它也把 cjk ext b/c的字符解釋成 surrogate pairs (兩個字符),所以照例一的代碼是得不出欲想結果的。workaround 是

'{if(length($1)==1 || length($1)==2 && $1 >="𠀀" && $1 <="𪘀")print $1 }'

𠀀 和 𪘀 是cjk ext b 區的首字和末字。

blog comments powered by Disqus