Markdown 目录(TOC)完整教程:手动创建与自动生成方法
写长文档的时候,有个目录(Table of Contents,简称 TOC)能让读者快速跳转到感兴趣的章节。Markdown 本身没有专门生成目录的语法,但通过几种方法完全可以实现,而且有些方法比想象中简单。
Markdown 目录的基本原理
Markdown 目录的本质就是一组内部链接(也叫锚点链接),每个链接指向页面内的某个标题。渲染器会把你的标题自动转换成 HTML 锚点,你只需要用链接语法指向它就行了。
基本格式是这样的:
## 目录
- [简介](#简介)
- [安装](#安装)
- [使用方法](#使用方法)
- [常见问题](#常见问题)
## 简介
这里是简介的内容...
## 安装
安装步骤...关键在于理解标题怎么变成锚点——不同平台的规则不一样,搞清楚这个,手动写目录就不难了。
不同平台的锚点生成规则
这是很多人容易忽略的地方:同一个标题,在 GitHub、GitLab、Bitbucket 上生成的锚点可能完全不同。我之前帮一个项目写 README 的时候,手动写了目录在本地的 GitLab 上显示正常,推到 GitHub 后目录全部失效了,排查了好一阵才发现是锚点规则不一样。
GitHub 锚点规则
GitHub 的规则最常用,也比较直观:
| 规则 | 示例 |
|---|---|
| 全部转小写 | ## Hello World → #hello-world |
| 空格变短横线 | ## Getting Started → #getting-started |
| 去掉标点符号 | ## What's New? → #whats-new |
| 连续短横线合并 | ## Cost ($) Analysis → #cost--analysis |
| 重复标题加编号 | 第二个 ## Example → #example-1 |
GitLab 锚点规则
GitLab 的规则和 GitHub 大体相似,但有个关键区别——GitLab 支持用 [[_TOC_]] 标签直接自动生成目录,不需要手动写:
## 文档标题
[[_TOC_]]
## 第一节
内容...
## 第二节
内容...[[_TOC_]] 会根据页面的所有标题自动生成一个可点击的目录。这个功能从 GitLab 14.x 开始支持,写 wiki 和 README 都能用。
Bitbucket 锚点规则
Bitbucket 比较特殊,它会给所有锚点加一个 markdown-header- 前缀:
- [简介](#markdown-header-简介)
- [安装步骤](#markdown-header-安装步骤)如果你的文档只在 Bitbucket 上用,记得加上这个前缀。
中文标题的处理
中文标题在 GitHub 和 GitLab 上一般直接使用 URL 编码后的形式。不过实测下来,直接在链接里写中文也能跳转:
## 目录
- [快速开始](#快速开始)
- [配置说明](#配置说明)
## 快速开始
## 配置说明大部分现代 Markdown 渲染器都支持中文锚点直接跳转。但如果你发现某些平台不支持,可以用 HTML 的 <a> 标签手动加锚点:
<a name="快速开始"></a>
## 快速开始
然后在目录里链接到这个锚点:
- [快速开始](#快速开始)手动创建 Markdown 目录
如果你文档不长,或者标题很少变动,手动写目录是最直接的方式。
基本步骤
- 先把文档的所有标题写好
- 在文档开头(或你想放目录的位置)添加一个"目录"章节
- 用列表 + 链接语法,逐个指向各个标题
## 目录
- [功能介绍](#功能介绍)
- [安装](#安装)
- [npm 安装](#npm-安装)
- [手动安装](#手动安装)
- [使用](#使用)
- [许可证](#许可证)
## 功能介绍
...
## 安装
...
### npm 安装
...
### 手动安装
...
## 使用
...
## 许可证
...嵌套的标题用缩进来表示层级关系,这样目录结构和文档结构就能对上。
说实话,手动写目录的问题不在于写,而在于维护——每次改了标题,都得记得去目录里同步更新。所以对于经常变动的长文档,我更建议用自动生成工具。
使用 VS Code 插件自动生成目录
如果你用 VS Code 写 Markdown(很多人都是),装个插件就能一键生成目录,而且还能在保存时自动更新。
Markdown All in One
这是最推荐的 VS Code Markdown 插件,由 Yu Zhang 开发,在 VS Code Marketplace 上安装量超过 600 万。
安装和使用:
- 在 VS Code 扩展商店搜索
Markdown All in One并安装 - 打开你的
.md文件,把光标放在想插入目录的位置 - 按
Ctrl+Shift+P打开命令面板,输入Markdown: Create Table of Contents - 目录就自动生成了
生成出来的目录就是普通的 Markdown 列表,格式和手动写的一模一样,所以放到 GitHub、GitLab 上都能正常显示。
自动更新功能:
默认情况下,每次保存文件时插件会自动更新目录。如果你不想自动更新(比如目录位置有自定义内容),可以在 VS Code 设置里关掉:
{
"markdown.extension.toc.updateOnSave": false
}我用了这个插件快两年了,总体很顺手。有一点要注意:如果你文档里有 HTML 标签混着 Markdown 标题,插件有时候识别会出偏差,这种情况需要手动检查一下生成的目录。
Auto Markdown TOC
另一个选择是 Auto Markdown TOC 插件(作者 huntertran),功能类似但更轻量。安装后右键菜单里会出现 "Markdown TOC" 选项,操作也很直观。
命令行工具自动生成 TOC
如果你需要在 CI/CD 环境里批量处理,或者不想依赖特定编辑器,命令行工具是更好的选择。
doctoc — 最流行的 TOC 生成工具
doctoc 是 Node.js 写的工具,GitHub 上超过 4000 star,是最广泛使用的 Markdown TOC 生成器。
# 安装
npm install -g doctoc
# 为当前目录下所有 Markdown 文件生成 TOC
doctoc .
# 只处理单个文件
doctoc README.mddoctoc 会在文件中插入特殊的注释标记,之后再次运行 doctoc 时会自动更新已有的 TOC,不会重复插入:
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [简介](#简介)
- [安装](#安装)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->这个标记机制很聪明——你不该手动编辑 TOC 内容,改了标题直接重新跑一遍 doctoc 就行。
markdown-toc — 灵活的 Node.js 工具
另一个 Node.js 工具,由 jonschlinkert 开发,被 NASA、Prisma、Mocha、Prettier 等知名项目使用。
# 安装
npm install -g markdown-toc
# 生成 TOC 并输出到终端
markdown-toc README.md
# 保存到文件
markdown-toc README.md > TOC.md和 doctoc 不同,markdown-toc 默认输出到标准输出,你可以灵活决定怎么使用它——粘贴到文件里也好,管道到其他工具也好。
gh-md-toc — 专为 GitHub 优化的 Shell 工具
如果你不想装 Node.js,这个纯 Shell 脚本就够用了。
# 下载(Linux)
wget https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc
chmod a+x gh-md-toc
# 下载(macOS)
curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
chmod a+x gh-md-toc
# 使用
./gh-md-toc README.md它还能处理远程 GitHub 上的文件,甚至 GitHub Wiki 页面:
./gh-md-toc https://github.com/user/repo/blob/main/README.md自动插入模式下,在文件里放上标记:
<!--ts-->
<!--te-->然后运行 ./gh-md-toc --insert README.md,TOC 就会被插入到这两个标记之间。之后每次改了标题,再跑一次命令就行。
md-toc — Python 用户的工具
如果你更习惯 Python 生态,md-toc 是一个功能完善的选择:
pip install md-toc
md_toc --in-place github --header-levels 6 README.md它支持 GitHub 和 GitLab 两种锚点生成模式,还能控制显示的标题层级。
在线 TOC 生成工具
不想装任何工具的话,在线生成器最方便:
- BitDownToc(bitdowntoc.derlin.ch):支持 GitHub、GitLab、dev.to、hashnode 等多种平台 profile,粘贴 Markdown 内容就能生成对应平台的 TOC。开源免费。
工具对比:选哪个?
| 工具 | 类型 | 支持平台 | 自动更新 | 依赖 |
|---|---|---|---|---|
| 手动编写 | — | 全部 | 无 | 无 |
| Markdown All in One | VS Code 插件 | 全部 | 保存时自动更新 | VS Code |
| Auto Markdown TOC | VS Code 插件 | 全部 | 手动触发 | VS Code |
| doctoc | CLI | GitHub/GitLab | 重新运行即更新 | Node.js |
| markdown-toc | CLI | GitHub | 输出到 stdout | Node.js |
| gh-md-toc | Shell 脚本 | GitHub | 标记间自动更新 | curl/wget |
| md-toc | CLI | GitHub/GitLab | 标记间自动更新 | Python |
| BitDownToc | 在线工具 | 多平台 | 手动复制 | 浏览器 |
GitLab [[_TOC_]] | 平台原生 | 仅 GitLab | 自动 | 无 |
选工具的建议:
- 写 GitHub README,用 VS Code 插件或者 doctoc 就够了
- 只用 GitLab,直接用
[[_TOC_]],最省事 - 需要在 CI 里自动化,用命令行工具配合脚本
- 偶尔用一次,在线工具最方便
用 GitHub Actions 自动维护 TOC
如果你有一个文档仓库,不想每次改标题后手动跑工具更新 TOC,可以用 GitHub Actions 实现全自动化。
下面这个配置会在你推送 Markdown 文件时自动更新 TOC:
name: Update TOC
on:
push:
branches: [main]
paths: ['**/*.md']
jobs:
toc:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- run: |
curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
chmod a+x gh-md-toc
./gh-md-toc --insert --no-backup README.md
rm gh-md-toc
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "Auto update markdown TOC"这样每次你 push 代码,Actions 会自动检查 Markdown 文件的 TOC 是否需要更新,如果需要就自动 commit 新版本。
重复标题和特殊字符的处理
实际写文档时,经常会遇到一些边缘情况。
重复标题
如果一个文档里有两个相同的标题,GitHub 会自动给第二个加上编号:
## 示例
这是第一个示例...
## 示例
这是第二个示例...对应的锚点分别是 #示例 和 #示例-1。在目录里链接时要注意这点:
- [示例(第一个)](#示例)
- [示例(第二个)](#示例-1)特殊字符
标题里有 $、()、? 等特殊字符时,GitHub 会把它们去掉:
## 版本 2.0 (重大更新)锚点是 #版本-20-重大更新,注意点号被保留了,但括号没了。
重复标题在不同平台的表现
| 平台 | 重复标题处理 |
|---|---|
| GitHub | 第二个加 -1,第三个加 -2,以此类推 |
| GitLab | 同样加编号后缀 |
| Bitbucket | 同样加编号后缀,但前缀是 markdown-header- |
如果不确定某个标题生成的锚点是什么,GitHub 上有个小技巧:把鼠标悬停在标题上,会出现一个链接图标,点一下就能看到实际的锚点了。
常见问题
GitHub README 支持 [TOC] 标签吗?
不支持。GitHub 不认识 [TOC] 这个标记(不像一些 Markdown 编辑器那样)。在 GitHub 上你只能用上面介绍的方法来创建目录——要么手动写,要么用工具生成。
为什么我的目录链接跳转不到对应标题?
最常见的原因是锚点格式不对。检查一下:
- 锚点是不是全小写了
- 空格是不是换成了短横线
- 有没有多余的标点符号没去掉
- 是不是 Bitbucket 环境忘了加
markdown-header-前缀
怎么让目录只显示特定层级的标题?
用 VS Code 的 Markdown All in One 插件,可以在设置里控制:
{
"markdown.extension.toc.levels": "2..4"
}这表示只显示 H2 到 H4 的标题。用命令行工具的话,doctoc 用 --maxlevel 参数,md-toc 用 --header-levels 参数。
GitHub 自动生成的目录和手写的有什么区别?
2021 年 4 月起,GitHub 在 Markdown 文件的头部自动显示一个"大纲"(Outline)菜单,列出所有标题。这个功能是 GitHub 前端自动生成的,不需要你在文件里写任何东西。但它只出现在 GitHub 网页上,不影响你的 Markdown 文件内容。
所以说,如果你在 GitHub 上写文档,文件里手动或工具生成的 TOC 是给所有场景用的(包括 clone 到本地看),GitHub 自带的 Outline 是只在网页上多一层导航。