强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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使用双字符标志(如 AAAB头部定义
flag num使用数字标志(如 12头部定义
flag char使用单字符标志(如 AB头部定义

7.3.3 SFX(后缀)规则详解

SFX <flag> <cross_product> <count>
SFX <flag> <strip> <append> <condition>
字段说明
flag标志字符
cross_productY 允许前后缀交叉组合,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 流水线中。