Aspell 拼写检查完全教程 / 第7章 创建自定义词典
第 7 章:创建自定义词典
本章深入讲解如何从零创建 Aspell 词典——包括词表格式、affix 规则文件、词典编译与压缩,以及发布维护流程。
7.1 词典创建流程总览
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 准备词表 │ ──→ │ 编写 affix │ ──→ │ 编译词典 │ ──→ │ 测试发布 │
│ (wordlist)│ │ 规则文件 │ │ (aspell │ │ │
│ │ │ (.aff) │ │ compile) │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
7.2 词表格式(Word List)
7.2.1 基本格式
词表是一个纯文本文件,每行一个单词:
# 注释行(以 # 开头)
word1
word2
word3
完整词表示例
cat > my_words.txt << 'EOF'
# 技术术语词表
api
backend
cache
database
devops
dockerfile
frontend
kubernetes
microservice
proxy
websocket
yaml
EOF
7.2.2 词表格式规范
| 规则 | 说明 |
|---|---|
| 编码 | UTF-8(推荐) |
| 行分隔 | Unix 换行符 (\n) |
| 大小写 | 小写为主,大写词匹配更严格 |
| 重复 | 不允许重复(编译时会报错) |
| 排序 | 建议字母序排序(非强制) |
| 注释 | # 开头的行为注释 |
| 空行 | 忽略 |
| 特殊字符 | 不支持(除非在 affix 文件中定义) |
7.2.3 带词性标注的词表
# 格式: word/flags
# flags 引用 affix 文件中的规则
cat > advanced_words.txt << 'EOF'
# 带词缀标记的词表
run/DGS # D=过去式, G=现在分词, S=第三人称单数
walk/DGS
play/DGS
happy/R # R=副词后缀 (-ly)
quick/R
EOF
7.3 Affix 文件(.aff)
Affix 文件定义词缀规则,使一个基础词形能自动匹配其变体(复数、时态等),从而大幅减小词表体积。
7.3.1 基本结构
# my_lang.aff — 自定义 affix 规则文件
# 字符集定义
name my_lang
charset utf-8
# flag 类型(long = 双字符 flag,num = 数字 flag)
flag long
# 词缀规则定义
SFX S Y 1
SFX S 0 s .
PFX P Y 1
PFX P 0 un .
7.3.2 词缀标志类型
| 标志 | 含义 | 位置 |
|---|---|---|
SFX | 后缀 (Suffix) | 单词末尾 |
PFX | 前缀 (Prefix) | 单词开头 |
flag long | 使用双字符标志(如 AA、AB) | 头部定义 |
flag num | 使用数字标志(如 1、2) | 头部定义 |
flag char | 使用单字符标志(如 A、B) | 头部定义 |
7.3.3 SFX(后缀)规则详解
SFX <flag> <cross_product> <count>
SFX <flag> <strip> <append> <condition>
| 字段 | 说明 |
|---|---|
flag | 标志字符 |
cross_product | Y 允许前后缀交叉组合,N 不允许 |
count | 该标志下的规则数量 |
strip | 要去除的部分(0 表示不删除) |
append | 要添加的部分 |
condition | 匹配条件(正则,. 匹配任何字符) |
英语复数后缀示例
# SFX 规则:添加复数 -s
SFX S Y 1
SFX S 0 s .
# 含义:
# flag = S
# cross_product = Y
# 1 条规则
# strip = 0 (不删除任何字符)
# append = s (添加 s)
# condition = . (任何字符都可以)
# 使用该规则
cat > words.txt << 'EOF'
word/S
book/S
EOF
# 编译后,word 会匹配 word、words
# book 会匹配 book、books
更复杂的后缀示例
# 英语 -ing 后缀(去掉 e 加 ing)
SFX G Y 2
SFX G e ing [^e] # 以非 e 结尾 → 加 ing
SFX G 0 ing e # 以 e 结尾 → 去 e 加 ing
# 英语 -ed 后缀
SFX D Y 2
SFX G e ed [^e]
SFX G 0 ed e
# 英语 -ly 副词后缀
SFX R Y 1
SFX R y ly [^y] # 以非 y 结尾 → y 变 ies
SFX R 0 ly y # 以 y 结尾 → 加 ly
7.3.4 PFX(前缀)规则详解
PFX <flag> <cross_product> <count>
PFX <flag> <strip> <append> <condition>
示例:否定前缀
# 添加否定前缀 un-
PFX U Y 1
PFX U 0 un .
# 添加否定前缀 dis-
PFX I Y 1
PFX I 0 dis .
# 添加否定前缀 in-(特殊:im- 在 b/m/p 前)
PFX N Y 2
PFX N 0 in [^bmp]
PFX N 0 im [bmp]
7.3.5 完整 Affix 文件示例
cat > tech_en.aff << 'EOF'
# tech_en.aff — 技术英语自定义 affix 规则
name tech_en
charset utf-8
flag long
# ===== 后缀规则 =====
# S = 复数 (-s)
SFX S Y 1
SFX S 0 s .
# G = 现在分词 (-ing)
SFX G Y 2
SFX G e ing [^e]
SFX G 0 ing e
# D = 过去式 (-ed)
SFX D Y 2
SFX D e ed [^e]
SFX D 0 ed e
# R = 副词 (-ly)
SFX R Y 2
SFX R y ly [^y]
SFX R 0 ly y
# T = 名词化 (-tion/-ation)
SFX T Y 2
SFX T e ion [^e]
SFX T 0 ation e
# ===== 前缀规则 =====
# U = 否定 (un-)
PFX U Y 1
PFX U 0 un .
# I = 否定 (in-)
PFX I Y 2
PFX I 0 in [^bmp]
PFX I 0 im [bmp]
EOF
7.3.6 词表与 Affix 文件配合
# tech_words.txt — 带标志的词表
cat > tech_words.txt << 'EOF'
# tech_en 词表
api
cache/SG
deploy/DGS
frontend
interface/SG
kubernetes
microservice/S
proxy/S
scale/DGS
test/DGS
unstable/U
EOF
展开后的完整词表:
api
cache → cache, caches
deploy → deploy, deploys, deployed, deploying
frontend
interface → interface, interfaces
kubernetes
microservice → microservice, microservices
proxy → proxy, proxies
scale → scale, scales, scaled, scaling
test → test, tests, tested, testing
unstable → unstable, unstable (U 无实际展开)
7.4 编译词典
7.4.1 编译流程
# 1. 准备文件
# my_dict.wordlist — 词表文件(带标志)
# my_dict.aff — affix 规则文件
# 2. 使用预压缩模式编译(推荐)
aspell --lang=en create master ./my_dict.cwl < my_dict.wordlist
# 3. 使用简单模式编译
aspell create master ./my_dict < my_dict.wordlist
7.4.2 编译选项
# 使用自定义 affix 文件
aspell --lang=en --aff-file=my_dict.aff create master ./my_dict.cwl < my_dict.wordlist
# 指定编码
aspell --encoding=utf-8 create master ./my_dict.cwl < my_dict.wordlist
# 详细输出
aspell --lang=en create master ./my_dict.cwl < my_dict.wordlist 2>&1
7.4.3 编译输出文件
编译后会生成以下文件:
ls -la my_dict.*
# my_dict.cwl — 压缩的词典数据文件
# my_dict.multi — 多词典配置文件(可选)
7.4.4 安装编译后的词典
# 复制到 Aspell 数据目录
sudo cp my_dict.cwl /usr/lib/aspell-0.60/
# 创建 multi 文件(如果需要)
cat > /usr/lib/aspell-0.60/my_dict.multi << 'EOF'
add my_dict
EOF
# 验证安装
aspell dump dicts | grep my_dict
echo "cache" | aspell -a -d my_dict
7.5 压缩原理
7.5.1 Aspell 词典压缩
Aspell 使用两种压缩策略:
| 策略 | 说明 | 文件格式 |
|---|---|---|
| 预压缩 (Pre-compressed) | 将 affix 展开后的完整词表压缩 | .cwl |
| 后压缩 (Post-compressed) | 只存储基础词形 + affix 规则,运行时展开 | .aspell |
7.5.2 后压缩(推荐用于大词典)
# 创建后压缩词典
prezip -d < my_dict.wordlist | \
aspell --lang=en create master ./my_dict.cwl
# 或使用 aspell 的默认压缩
aspell --lang=en create master ./my_dict < my_dict.wordlist
7.5.3 prezip 工具
# prezip 是 Aspell 附带的压缩工具
# 压缩词表
prezip < my_dict.wordlist > my_dict.cwl
# 解压
prezip -d < my_dict.cwl > my_dict.wordlist
# 统计压缩比
original=$(wc -c < my_dict.wordlist)
compressed=$(wc -c < my_dict.cwl)
echo "压缩比: $(echo "scale=2; $compressed * 100 / $original" | bc)%"
7.6 从现有词典提取词表
7.6.1 导出现有词典
# 导出英语主词典
aspell -d en_US dump master > en_US_words.txt
# 统计单词数
wc -l en_US_words.txt
# 查看前 20 个单词
head -20 en_US_words.txt
# 按字母排序
sort en_US_words.txt > en_US_words_sorted.txt
7.6.2 从文本生成词表
#!/bin/bash
# generate_wordlist.sh — 从文本文件生成候选词表
INPUT="$1"
OUTPUT="${2:-wordlist.txt}"
# 提取所有单词,去重,排序
cat "$INPUT" | \
tr -cs '[:alpha:]' '\n' | \
tr '[:upper:]' '[:lower:]' | \
sort -u | \
grep -E '^.{2,}$' | \ # 过滤掉过短的单词
grep -v '^#' > "$OUTPUT"
echo "生成词表: $(wc -l < "$OUTPUT") 个单词"
7.6.3 过滤已有词典中的单词
#!/bin/bash
# filter_existing.sh — 从候选词表中去除已在词典中的单词
CANDIDATES="$1"
DICT_LANG="${2:-en}"
# 获取主词典中的单词
EXISTING=$(aspell -d "$DICT_LANG" dump master)
# 找出不在主词典中的单词
comm -23 <(sort "$CANDIDATES") <(echo "$EXISTING" | sort)
7.7 业务场景
场景 1:项目专用技术词典
#!/bin/bash
# create_tech_dict.sh — 创建技术项目词典
DICT_DIR="./dict"
mkdir -p "$DICT_DIR"
# 1. 创建词表
cat > "$DICT_DIR/tech_en.wordlist" << 'EOF'
# 技术术语
api
backend
cache/SG
containerize/DGS
deploy/DGS
devops
dockerfile/S
frontend
grpc
json
kubernetes
microservice/S
oauth
proxy/S
restful
scale/DGS
serverless
websocket/S
yaml
EOF
# 2. 创建 affix 文件
cat > "$DICT_DIR/tech_en.aff" << 'EOF'
name tech_en
charset utf-8
flag long
SFX S Y 1
SFX S 0 s .
SFX G Y 2
SFX G e ing [^e]
SFX G 0 ing e
SFX D Y 2
SFX D e ed [^e]
SFX D 0 ed e
EOF
# 3. 编译
cd "$DICT_DIR"
aspell --lang=en --aff-file=tech_en.aff \
create master ./tech_en.cwl < tech_en.wordlist
# 4. 安装
sudo cp tech_en.cwl /usr/lib/aspell-0.60/
echo "add tech_en" | sudo tee /usr/lib/aspell-0.60/tech_en.multi
echo "技术词典已创建并安装"
场景 2:医学英语词典
#!/usr/bin/env python3
"""generate_medical_dict.py — 从医学术语列表生成 Aspell 词典"""
import subprocess
import os
# 医学术语词表
MEDICAL_TERMS = [
"acetaminophen",
"anemia",
"antibiotic",
"biopsy",
"cardiology",
"chemotherapy",
"diagnosis", # 诊断
"dyspnea", # 呼吸困难
"edema", # 水肿
"hemoglobin",
"hypertension",
"immunotherapy",
"inflammation",
"metastasis",
"oncology",
"pathology",
"pharmacology",
"prognosis",
"radiology",
"tachycardia",
"thrombosis",
]
DICT_DIR = "./medical_dict"
os.makedirs(DICT_DIR, exist_ok=True)
# 写入词表
wordlist_path = os.path.join(DICT_DIR, "medical_en.wordlist")
with open(wordlist_path, 'w') as f:
f.write("# Medical English Dictionary\n")
for term in sorted(MEDICAL_TERMS):
f.write(f"{term}\n")
print(f"词表已生成: {wordlist_path} ({len(MEDICAL_TERMS)} 个术语)")
# 创建 affix 文件
affix_path = os.path.join(DICT_DIR, "medical_en.aff")
with open(affix_path, 'w') as f:
f.write("""name medical_en
charset utf-8
flag long
SFX S Y 1
SFX S 0 s .
SFX G Y 2
SFX G e ing [^e]
SFX G 0 ing e
SFX D Y 2
SFX D e ed [^e]
SFX D 0 ed e
SFX R Y 1
SFX R y ly [^y]
SFX R 0 ly y
""")
# 编译词典
cmd = [
'aspell', '--lang=en',
f'--aff-file={affix_path}',
'create', 'master',
os.path.join(DICT_DIR, 'medical_en.cwl')
]
with open(wordlist_path) as f:
result = subprocess.run(cmd, stdin=f, capture_output=True, text=True)
if result.returncode == 0:
print("词典编译成功")
else:
print(f"编译失败: {result.stderr}")
场景 3:多语言自定义词典
#!/bin/bash
# create_multilang_dict.sh — 创建多语言组合词典
DICT_BASE="/usr/lib/aspell-0.60"
# 创建自定义英语词典
cat > my_en.wordlist << 'EOF'
api
docker
kubernetes
EOF
aspell --lang=en create master ./my_en.cwl < my_en.wordlist
sudo cp my_en.cwl "$DICT_BASE/"
# 创建 multi 文件组合多个词典
cat > "$DICT_BASE/my_en.multi" << 'EOF'
add en_US-aspell
add my_en
EOF
# 验证
aspell -d my_en dump master | tail -5
echo "kubernetes" | aspell -a -d my_en
7.8 词典测试
7.8.1 基本测试
# 测试新词典是否正确加载
echo "kubernetes" | aspell -a -d my_dict
# 期望输出: *(拼写正确)
# 测试不在词典中的单词
echo "kubernetees" | aspell -a -d my_dict
# 期望输出: & kubernetees ...(给出建议)
7.8.2 自动化测试脚本
#!/bin/bash
# test_dict.sh — 自动化词典测试
DICT="$1"
PASS=0
FAIL=0
# 应该通过的单词
PASS_WORDS=("api" "cache" "docker" "kubernetes")
# 应该失败的单词
FAIL_WORDS=("apI" "cach" "dockerr" "kubernetees")
echo "测试词典: $DICT"
echo "========="
# 测试应该通过的单词
for word in "${PASS_WORDS[@]}"; do
result=$(echo "$word" | aspell -a -d "$DICT" 2>/dev/null | head -1)
if echo "$result" | grep -q '^\*'; then
echo "✓ PASS: $word"
((PASS++))
else
echo "✗ FAIL: $word (应通过但未通过)"
((FAIL++))
fi
done
# 测试应该失败的单词
for word in "${FAIL_WORDS[@]}"; do
result=$(echo "$word" | aspell -a -d "$DICT" 2>/dev/null | head -1)
if echo "$result" | grep -q '^\*'; then
echo "✗ FAIL: $word (应失败但通过了)"
((FAIL++))
else
echo "✓ PASS: $word (正确识别为拼写错误)"
((PASS++))
fi
done
echo ""
echo "结果: $PASS 通过, $FAIL 失败"
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
7.9 发布与维护
7.9.1 词典项目结构
my-dictionary/
├── README.md # 文档
├── LICENSE # 许可证
├── Makefile # 编译脚本
├── src/
│ ├── my_dict.wordlist # 词表源文件
│ └── my_dict.aff # affix 规则
├── build/ # 编译输出(.gitignore)
│ ├── my_dict.cwl
│ └── my_dict.multi
├── test/
│ ├── test_pass.txt # 应该通过的测试词
│ └── test_fail.txt # 应该失败的测试词
└── scripts/
├── build.sh # 编译脚本
└── install.sh # 安装脚本
7.9.2 Makefile
# Makefile — Aspell 词典构建
DICT_NAME = my_dict
LANG = en
AFF_FILE = src/$(DICT_NAME).aff
WORDLIST = src/$(DICT_NAME).wordlist
BUILD_DIR = build
OUTPUT = $(BUILD_DIR)/$(DICT_NAME).cwl
.PHONY: all clean install test
all: $(OUTPUT)
$(OUTPUT): $(WORDLIST) $(AFF_FILE)
@mkdir -p $(BUILD_DIR)
aspell --lang=$(LANG) --aff-file=$(AFF_FILE) \
create master $@ < $<
test: $(OUTPUT)
@bash scripts/test.sh $(DICT_NAME) $(BUILD_DIR)
install: $(OUTPUT)
sudo cp $(OUTPUT) /usr/lib/aspell-0.60/
echo "add $(DICT_NAME)" | sudo tee /usr/lib/aspell-0.60/$(DICT_NAME).multi
@echo "词典已安装"
clean:
rm -rf $(BUILD_DIR)
7.9.3 词典版本管理
# 在词表中维护版本信息
cat > src/my_dict.wordlist << 'EOF'
# my_dict v1.2.0 — 2026-05-10
# 变更: 添加 Kubernetes 相关术语
api
cache/SG
kubernetes
EOF
# 使用 Git 标签管理版本
git tag -a v1.2.0 -m "Release v1.2.0: 添加 Kubernetes 术语"
7.10 本章小结
| 要点 | 说明 |
|---|---|
| 词表格式 | 纯文本,每行一个单词,可带 affix 标志 |
| Affix 文件 | 定义前缀/后缀规则,减少词表体积 |
| 编译 | aspell create master output.cwl < wordlist |
| 压缩 | 使用 prezip 工具或 aspell 内置压缩 |
| 测试 | 自动化测试通过/失败用例 |
| 发布 | 标准化项目结构,使用 Makefile 自动化 |
下一步
→ 第 8 章:编辑器与 CI 集成 — 学习将 Aspell 集成到编辑器和 CI/CD 流水线中。