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

Aspell 拼写检查完全教程 / 第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 使用中的最佳实践、性能优化和常见问题处理。