第9章 Docker 中使用 Aspell
第 9 章:Docker 中使用 Aspell
本章介绍如何在 Docker 容器中运行 Aspell,包括镜像构建、批量文档检查、CI/CD 集成和自动化运维场景。
9.1 为什么在 Docker 中使用 Aspell
| 场景 | 好处 |
|---|---|
| 环境一致性 | 无论本地、CI、服务器环境,结果完全一致 |
| 免安装依赖 | 无需在宿主机安装 Aspell 和词典包 |
| 批量处理 | 一次性检查整个文档仓库 |
| CI/CD 集成 | 作为独立的 CI 步骤运行 |
| 多语言环境 | 预装多种语言词典的镜像 |
9.2 基础 Docker 镜像
9.2.1 最小镜像(Alpine)
# Dockerfile — 基于 Alpine 的最小 Aspell 镜像
FROM alpine:3.20
RUN apk add --no-cache \
aspell \
aspell-en
WORKDIR /data
# 入口点设为 aspell
ENTRYPOINT ["aspell"]
CMD ["--help"]
# 构建镜像
docker build -t aspell:alpine .
# 测试
echo "teh" | docker run --rm -i aspell:alpine -a
# 检查文件
docker run --rm -v $(pwd):/data aspell:alpine check document.txt
9.2.2 完整镜像(Ubuntu,含多语言)
# Dockerfile — 完整的多语言 Aspell 镜像
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
aspell \
aspell-en \
aspell-de \
aspell-fr \
aspell-es \
aspell-pt \
aspell-ru \
&& rm -rf /var/lib/apt/lists/*
# 验证安装
RUN aspell --version && aspell dump dicts
WORKDIR /data
ENTRYPOINT ["aspell"]
CMD ["--help"]
# 构建
docker build -t aspell:full .
# 使用德语词典
echo "Hallo" | docker run --rm -i aspell:full -a -d de
# 使用法语词典
echo "bonjour" | docker run --rm -i aspell:full -a -d fr
9.2.3 带自定义词典的镜像
# Dockerfile — 包含项目词典
FROM aspell:alpine
# 复制自定义词典
COPY .aspell/ /root/.aspell/
# 创建个人词典目录
RUN mkdir -p /root/.aspell
WORKDIR /data
ENTRYPOINT ["aspell"]
CMD ["--help"]
9.3 批量文档检查
9.3.1 检查整个目录
# 检查当前目录下所有 .md 文件
docker run --rm -v $(pwd):/data aspell:alpine \
sh -c 'find /data -name "*.md" -exec aspell list --mode=markdown {} \;'
9.3.2 带报告的批量检查
#!/bin/bash
# docker-spellcheck.sh — 使用 Docker 批量检查并生成报告
IMAGE="aspell:alpine"
DOCS_DIR="./docs"
REPORT_FILE="spellcheck-report.txt"
echo "=== Aspell 批量拼写检查 ===" > "$REPORT_FILE"
echo "时间: $(date)" >> "$REPORT_FILE"
echo "" >> "$REPORT_FILE"
TOTAL_FILES=0
TOTAL_ERRORS=0
while IFS= read -r -d '' file; do
((TOTAL_FILES++))
REL_PATH="${file#./}"
# 在 Docker 中运行检查
ERRORS=$(docker run --rm -v "$(pwd):/data" "$IMAGE" \
list --mode=markdown < "$file" 2>/dev/null)
if [ -n "$ERRORS" ]; then
ERROR_COUNT=$(echo "$ERRORS" | sort -u | wc -l)
((TOTAL_ERRORS += ERROR_COUNT))
echo "✗ $REL_PATH ($ERROR_COUNT 个错误)" | tee -a "$REPORT_FILE"
echo "$ERRORS" | sort -u | sed 's/^/ /' >> "$REPORT_FILE"
echo "" >> "$REPORT_FILE"
else
echo "✓ $REL_PATH" | tee -a "$REPORT_FILE"
fi
done < <(find "$DOCS_DIR" -name "*.md" -print0)
echo "" >> "$REPORT_FILE"
echo "=== 统计 ===" >> "$REPORT_FILE"
echo "检查文件: $TOTAL_FILES" >> "$REPORT_FILE"
echo "拼写错误: $TOTAL_ERRORS" >> "$REPORT_FILE"
echo ""
echo "报告已保存到: $REPORT_FILE"
9.3.3 并行批量检查
#!/bin/bash
# docker-spellcheck-parallel.sh — 并行检查提速
IMAGE="aspell:alpine"
MAX_JOBS=4
check_file() {
local file="$1"
local errors
errors=$(docker run --rm -v "$(pwd):/data" "$IMAGE" \
list --mode=markdown < "$file" 2>/dev/null)
if [ -n "$errors" ]; then
echo "ERROR:$file:$(echo "$errors" | sort -u | tr '\n' '|')"
else
echo "OK:$file"
fi
}
export -f check_file
export IMAGE
# 并行执行
find . -name "*.md" -print0 | \
xargs -0 -P "$MAX_JOBS" -I {} bash -c 'check_file "$@"' _ {} | \
while IFS=: read -r status file words; do
if [ "$status" = "ERROR" ]; then
echo "✗ $file"
echo "$words" | tr '|' '\n' | sed 's/^/ /'
else
echo "✓ $file"
fi
done
9.4 CI/CD Docker 集成
9.4.1 GitHub Actions 使用 Docker
# .github/workflows/spellcheck-docker.yml
name: Spellcheck (Docker)
on: [push, pull_request]
jobs:
spellcheck:
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
steps:
- name: Install Aspell
run: |
apt-get update
apt-get install -y aspell aspell-en git
- name: Checkout
uses: actions/checkout@v4
- name: Run spell check
run: |
EXIT_CODE=0
while IFS= read -r file; do
ERRORS=$(aspell list \
--mode=markdown \
--personal=.aspell/project.pws \
< "$file" 2>/dev/null)
if [ -n "$ERRORS" ]; then
echo "::error file=$file::Spelling errors found"
echo "$ERRORS" | sort -u
EXIT_CODE=1
fi
done < <(find . -name "*.md" -not -path "./node_modules/*")
exit $EXIT_CODE
9.4.2 GitLab CI 使用 Docker
# .gitlab-ci.yml
spellcheck:
stage: lint
image: alpine:3.20
before_script:
- apk add --no-cache aspell aspell-en
script:
- |
for file in $(find . -name "*.md"); do
ERRORS=$(aspell list --mode=markdown < "$file" 2>/dev/null)
if [ -n "$ERRORS" ]; then
echo "ERROR in $file:"
echo "$ERRORS" | sort -u
exit 1
fi
done
only:
- merge_requests
9.4.3 自建 Docker 镜像用于 CI
# Dockerfile.ci — 专门用于 CI 的 Aspell 镜像
FROM alpine:3.20
# 安装基础工具
RUN apk add --no-cache \
aspell \
aspell-en \
bash \
findutils \
grep
# 复制 CI 脚本
COPY scripts/check-spelling.sh /usr/local/bin/check-spelling
RUN chmod +x /usr/local/bin/check-spelling
# 创建默认词典目录
RUN mkdir -p /etc/aspell
WORKDIR /workspace
ENTRYPOINT ["check-spelling"]
#!/bin/bash
# scripts/check-spelling.sh — CI 入口脚本
set -euo pipefail
DOCS_DIR="${1:-.}"
PERSONAL_DICT="${PERSONAL_DICT:-/etc/aspell/project.pws}"
LANG="${LANG:-en}"
echo "=== Docker Spellcheck ==="
echo "目录: $DOCS_DIR"
echo "语言: $LANG"
echo "词典: $PERSONAL_DICT"
echo ""
ARGS=(
"list"
"--mode=markdown"
"-d" "$LANG"
)
if [ -f "$PERSONAL_DICT" ]; then
ARGS+=("--personal" "$PERSONAL_DICT")
fi
EXIT_CODE=0
TOTAL=0
ERRORS=0
while IFS= read -r -d '' file; do
((TOTAL++))
result=$(aspell "${ARGS[@]}" < "$file" 2>/dev/null)
if [ -n "$result" ]; then
echo "✗ ${file#./}"
echo "$result" | sort -u | sed 's/^/ /'
((ERRORS++))
fi
done < <(find "$DOCS_DIR" -name "*.md" -print0)
echo ""
echo "=== 结果 ==="
echo "文件: $TOTAL | 错误: $ERRORS"
[ $ERRORS -eq 0 ] && exit 0 || exit 1
# 构建并使用
docker build -t aspell-ci -f Dockerfile.ci .
# 检查当前项目
docker run --rm -v $(pwd):/workspace aspell-ci .
# 指定目录和词典
docker run --rm \
-v $(pwd):/workspace \
-e PERSONAL_DICT=/workspace/.aspell/project.pws \
aspell-ci docs/
9.5 Docker Compose 集成
# docker-compose.yml — 包含拼写检查服务
version: '3.8'
services:
spellcheck:
build:
context: .
dockerfile: Dockerfile.ci
volumes:
- .:/workspace
- ./.aspell:/etc/aspell
environment:
- LANG=en
- PERSONAL_DICT=/etc/aspell/project.pws
command: ["/workspace/docs"]
# 其他服务...
web:
image: nginx
volumes:
- ./public:/usr/share/nginx/html
# 运行拼写检查
docker-compose run --rm spellcheck docs/
# 只检查变更文件
docker-compose run --rm spellcheck --changed
9.6 自动化脚本
9.6.1 Docker 一键检查脚本
#!/bin/bash
# spell-docker.sh — 一键 Docker 拼写检查
set -euo pipefail
IMAGE="aspell:alpine"
DICTIONARIES=""
PERSONAL_DICT=""
MODE="markdown"
CHECK_DIR="."
usage() {
cat << EOF
用法: $0 [选项] [目录]
选项:
-i, --image IMAGE Docker 镜像 (默认: $IMAGE)
-d, --dict LANG 语言 (默认: en)
-p, --personal FILE 个人词典路径
-m, --mode MODE 过滤器模式 (默认: markdown)
-f, --fix 交互式修复
-h, --help 显示帮助
示例:
$0 docs/ # 检查 docs 目录
$0 -d de docs/de/ # 德语检查
$0 -p .aspell/custom.pws . # 使用自定义词典
EOF
exit 0
}
FIX_MODE=false
while [[ $# -gt 0 ]]; do
case $1 in
-i|--image) IMAGE="$2"; shift 2 ;;
-d|--dict) DICTIONARIES="$2"; shift 2 ;;
-p|--personal) PERSONAL_DICT="$2"; shift 2 ;;
-m|--mode) MODE="$2"; shift 2 ;;
-f|--fix) FIX_MODE=true; shift ;;
-h|--help) usage ;;
*) CHECK_DIR="$1"; shift ;;
esac
done
# 构建运行参数
DOCKER_ARGS=(
--rm
-v "$(realpath "$CHECK_DIR"):/data:ro"
)
ASPELL_ARGS=(
list
"--mode=$MODE"
)
if [ -n "$DICTIONARIES" ]; then
ASPELL_ARGS+=("-d" "$DICTIONARIES")
fi
if [ -n "$PERSONAL_DICT" ]; then
DOCKER_ARGS+=(-v "$(realpath "$PERSONAL_DICT"):/dict.pws:ro")
ASPELL_ARGS+=("--personal" "/dict.pws")
fi
# 运行检查
echo "=== Docker Spellcheck ==="
echo "镜像: $IMAGE"
echo "目录: $CHECK_DIR"
echo ""
EXIT_CODE=0
TOTAL=0
ERRORS=0
while IFS= read -r -d '' file; do
((TOTAL++))
rel_path="${file#$(realpath "$CHECK_DIR")/}"
result=$(docker run "${DOCKER_ARGS[@]}" "$IMAGE" \
"${ASPELL_ARGS[@]}" < "$file" 2>/dev/null || true)
if [ -n "$result" ]; then
echo "✗ $rel_path"
echo "$result" | sort -u | sed 's/^/ /'
((ERRORS++))
EXIT_CODE=1
fi
done < <(find "$(realpath "$CHECK_DIR")" -name "*.md" -print0 2>/dev/null)
echo ""
echo "=== 结果 ==="
echo "文件: $TOTAL | 有错误: $ERRORS"
if [ $EXIT_CODE -ne 0 ]; then
echo ""
echo "提示: 使用 -p 选项指定个人词典来忽略专有名词"
fi
exit $EXIT_CODE
# 使用示例
./spell-docker.sh docs/
./spell-docker.sh -d de -p .aspell/de.pws docs/de/
./spell-docker.sh -i aspell:full -m tex papers/
9.6.2 Watch 模式(文件变更自动检查)
#!/bin/bash
# spell-watch.sh — 文件变更时自动运行 Docker 拼写检查
IMAGE="aspell:alpine"
WATCH_DIR="${1:-.}"
DEBOUNCE=2
echo "=== 监控模式 ==="
echo "目录: $WATCH_DIR"
echo "镜像: $IMAGE"
echo "按 Ctrl+C 退出"
echo ""
if command -v inotifywait &>/dev/null; then
# Linux (inotify)
while inotifywait -r -e modify,create "$WATCH_DIR" --include '\.md$'; do
sleep "$DEBOUNCE"
echo "检测到变更,运行检查..."
./spell-docker.sh "$WATCH_DIR" 2>/dev/null || true
echo ""
done
elif command -v fswatch &>/dev/null; then
# macOS (fswatch)
fswatch -r --include '\.md$' "$WATCH_DIR" | while read -r file; do
sleep "$DEBOUNCE"
echo "检测到变更: $file"
./spell-docker.sh "$WATCH_DIR" 2>/dev/null || true
echo ""
done
else
echo "需要 inotifywait (Linux) 或 fswatch (macOS)"
echo "安装: sudo apt-get install inotify-tools"
exit 1
fi
9.7 Docker 中的性能优化
9.7.1 多阶段构建减小镜像体积
# Dockerfile.multi-stage — 多阶段构建
FROM alpine:3.20 AS builder
RUN apk add --no-cache aspell aspell-en
# 编译自定义词典
COPY src/tech.wordlist /tmp/
RUN aspell --lang=en create master /tmp/tech.cwl < /tmp/tech.wordlist
# 最终镜像
FROM alpine:3.20
RUN apk add --no-cache aspell aspell-en
# 只复制编译后的词典
COPY --from=builder /tmp/tech.cwl /usr/lib/aspell-0.60/
WORKDIR /data
ENTRYPOINT ["aspell"]
9.7.2 缓存优化
# GitHub Actions 中缓存 Docker 层
- name: Build Aspell image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: aspell:local
cache-from: type=gha
cache-to: type=gha,mode=max
9.7.3 并行容器检查
#!/bin/bash
# parallel-docker-check.sh — 并行运行多个容器
IMAGE="aspell:alpine"
MAX_PARALLEL=4
check_in_container() {
local file="$1"
local errors
errors=$(docker run --rm -v "$(pwd):/data:ro" "$IMAGE" \
list --mode=markdown < "$file" 2>/dev/null)
if [ -n "$errors" ]; then
echo "✗ $file"
echo "$errors" | sort -u | sed 's/^/ /'
fi
}
export -f check_in_container
export IMAGE
find . -name "*.md" -print0 | \
xargs -0 -P "$MAX_PARALLEL" -I {} bash -c 'check_in_container "$@"' _ {}
9.8 生产环境部署
9.8.1 定时检查服务
# docker-compose.cron.yml
version: '3.8'
services:
spellcheck-cron:
build:
context: .
dockerfile: Dockerfile.ci
volumes:
- .:/workspace
- ./.aspell:/etc/aspell
- ./reports:/reports
environment:
- REPORT_DIR=/reports
entrypoint: ["/bin/bash", "-c"]
command:
- |
while true; do
REPORT_FILE="$REPORT_DIR/report-$(date +%Y%m%d-%H%M%S).txt"
echo "Running spellcheck at $(date)" > "$REPORT_FILE"
check-spelling /workspace/docs >> "$REPORT_FILE" 2>&1 || true
echo "Report saved: $REPORT_FILE"
sleep 3600 # 每小时运行一次
done
restart: unless-stopped
9.8.2 与 Webhook 集成
#!/usr/bin/env python3
"""webhook_spellcheck.py — 接收 Webhook 触发 Docker 拼写检查"""
from flask import Flask, request, jsonify
import subprocess
import os
app = Flask(__name__)
ASPELL_IMAGE = "aspell:alpine"
DOCS_DIR = os.environ.get("DOCS_DIR", "/workspace/docs")
@app.route('/webhook/spellcheck', methods=['POST'])
def trigger_spellcheck():
"""接收 Webhook 触发拼写检查"""
data = request.get_json()
# 可以根据 payload 决定检查范围
target = data.get('path', DOCS_DIR)
# 运行 Docker 检查
result = subprocess.run(
['docker', 'run', '--rm',
'-v', f'{os.getcwd()}:/workspace:ro',
ASPELL_IMAGE,
'list', '--mode=markdown'],
capture_output=True,
text=True,
timeout=300
)
errors = result.stdout.strip().split('\n') if result.stdout.strip() else []
return jsonify({
'status': 'error' if errors else 'ok',
'errors': errors,
'error_count': len(errors)
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
9.9 业务场景
场景 1:开源项目 CI 检查
# .github/workflows/spellcheck.yml
name: Spellcheck
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Aspell image
run: docker build -t aspell-ci -f Dockerfile.ci .
- name: Run spellcheck
run: |
docker run --rm \
-v ${{ github.workspace }}:/workspace \
-v ${{ github.workspace }}/.aspell:/etc/aspell \
aspell-ci /workspace/docs
场景 2:文档发布前检查
#!/bin/bash
# pre-publish.sh — 发布前 Docker 检查
echo "=== 发布前拼写检查 ==="
# 1. 构建镜像
docker build -t aspell-check -f Dockerfile.ci .
# 2. 运行检查
if docker run --rm \
-v $(pwd):/workspace \
-v $(pwd)/.aspell:/etc/aspell \
aspell-check /workspace/docs; then
echo "✓ 拼写检查通过"
else
echo "✗ 拼写检查失败,请修正后重试"
exit 1
fi
# 3. 继续发布流程
echo "开始发布..."
场景 3:团队共享检查环境
# Dockerfile.team — 团队共享的标准化检查环境
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
aspell \
aspell-en \
aspell-de \
aspell-fr \
aspell-es \
aspell-zh \
&& rm -rf /var/lib/apt/lists/*
# 安装团队词典
COPY team-dict/ /opt/team-dict/
# 设置全局配置
COPY aspell.conf /etc/aspell.conf
WORKDIR /data
ENTRYPOINT ["aspell"]
9.10 本章小结
| 要点 | 说明 |
|---|---|
| Alpine 镜像 | 最小体积,适合 CI 和批量检查 |
| Ubuntu 镜像 | 完整功能,多语言支持 |
| 批量检查 | find + Docker 循环,或并行 xargs |
| CI 集成 | GitHub Actions / GitLab CI 直接使用 Docker 镜像 |
| 性能优化 | 多阶段构建、层缓存、并行容器 |
| 生产部署 | 定时 Cron 服务、Webhook 触发 |
下一步
→ 第 10 章:最佳实践 — 总结 Aspell 使用中的最佳实践、性能优化和常见问题处理。