Markdown 语法基础

本文最后更新于:2020 年 02 月 24 日 星期一

这篇文章原本是 2016 年最开始使用 Markdown 对 Markdown 的基本语法都不了解时写的,当时希望通过这一篇文章学习、尝试 … 这也是我博客的第一篇文章

但是你现在看到的这篇是 2020 年重制版,补充了我这 4 年对 Markdown 语法的认知,特别是过去没有区分清楚的“标准语法”和“扩展语法”。

(重制它是因为感觉我原本写的东西太扯了。)


Markdown 是什么

很多刚开始接触 Markdown 或者没有接触过 Markdown 的同学可能不理解 Markdown 是什么,很多人或许会以为 Markdown 是一种文本编辑软件(类似 Microsoft Word ),或者以为 Markdown 是一种对文本内容和格式的定义(类似 LaTeX ),但其实都不是。尽管很多介绍 Markdown 的文章都喜欢将 Markdown 和 Microsoft Word 或 LaTeX 做对比,但其实 Markdown 与它们几乎没有任何相似性,自然也没有对比的意义。

Markdown 其实可以理解为 HTML 的子集的方言。按照 Markdown 的发明者 John Gruber 的说法:

Markdown is a text-to-HTML conversion tool for web writers.
Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).

译:

Markdown 是面向 Web 作家的一个从文本到 HTML 的转换工具。
Markdown 允许你使用易于阅读,易于编写的纯文本格式进行编写,然后将其转换为结构上有效的 XHTML(或 HTML )。

Markdown 的意义,其实就是方便人们写 HTML 。网页上不能方便嵌入一个 Microsoft Word 写的文档,但是可以无缝嵌入一段由 Markdown 转换而成的 HTML 代码,所以为什么基于 Web 的文档、博客、基于 Web 的富文本编辑器、 … 都喜欢使用 Markdown 。

实际上 Markdown 的语法与 HTML 是一一对应的。了解 HTML 的人应该都清楚, HTML 代码只能定义文章的结构,不定义其样式或者格式。所以同样的, Markdown 代码也仅仅是对文章结构的定义,至于它呈现怎样的样式,取决于展示环境对样式的定义(比如加载到这个页面的 CSS )。所以你经常会发现你在你的 Markdown 编辑器上看到的和你上传到 GitHub 的或者部署到你博客的显示效果不同。

作为建议,了解 Markdown 之前可以先了解 HTML ,下文也会经常介绍到它们的对应关系。

块元素

块元素就是对应 HTML 里面默认 display: block 的元素。比如标题、段落、有序列表、无序列表、引用块、 …

Markdown 中块元素之间需要有至少一行“空行”分隔。“空行”的意思是“看起来空的行”,所以只含空格、制表符的行也是“空行”。

最简单的一种块元素就是“段落”。

段落和换行

段落是简单的一行或连续的多行文本。对应 HTML 里的 <p></p> 元素。

示例如下:

第一个段落:只要像这样随便写一行。

第二个段落:
或者也可以这样分多行写。
同一个段落的第二行。
同一个段落的第三行。

以上示例的效果:

第一个段落:只要像这样随便写一行。

第二个段落:
或者也可以这样分多行写。
同一个段落的第二行。
同一个段落的第三行。

可以看到,第二个段落显示出来的文本没有换行,这很正常, 这是特性不是缺陷 ,因为第二个段落大概会被转换为这样的 HTML 代码:

<p>
	第二个段落:
	或者也可以这样分多行写。
	同一个段落的第二行。
	同一个段落的第三行。
</p>

对 HTML 有了解的同学应该明白,在代码中的换行是不会反映到呈现的效果中的。这样设计的意义是允许将一个较长的段落拆分到多个代码行中,以增加可读性,因为多数文本编辑器默认情况下不会像 Microsoft Word 一样自动换行。

如果需要在 Markdown 中输入一个“能显示出来的”换行,你只需要在换行前输入两个空格。类似这样:

这是一个段落<空格><空格>
内的换行

示例代码中的“<空格>”是为了示意方便,实际上它原本应该是透明的。以上代码效果如下:

这是一个段落
内的换行

“两个空格加一个换行符”会被转化为 HTML 中的 <br> ,即段落内换行。可以看到一般情况下段落内换行的行距比段落之间的距离小得多,这是一种层级较低的分隔方式。使用时一定要结合具体语义, 不要混用分段和换行

以上关于段落和换行的讨论是基于“标准”的,但是十分不幸的是 Markdown 的“实现”比较混乱。目前已经出现了一些令人担忧的潮流:在一些实现中单纯的换行(没有两个空格)也会被翻译为“真正的换行”(即 <br> ),这导致如果你不想在段落中换行就必须写特别长的一行,比如 Hexo 默认就是这样。这些实现的理由可能是“人性化”、降低使用门槛或者“看起来更直观和符合直觉”,但其实除了给 Markdown 引入混乱,没有任何意义。这些错误的实现中的一部分是可配置为标准做法的(即两个空格加一个换行为换行),比如 Hexo ,所以如果在这方面遇到麻烦,可以尝试搜索一下所使用的实现是如何配置的。

标题

在 Markdown 中一般使用 1 - 6 个 # 表示 1 - 6 级标题(没有七级标题),在几乎所有的实现中, # 与标题之间必须要有一个空格。类似这样:

# 一级标题

## 二级标题

### 三级标题

#### 四级标题

##### 五级标题

###### 六级标题

它们分别对应 HTML 中的 <h1></h1><h6></h6>

为了美观,你也可以在标题后面加上同等数量的 # 作为标题的关闭符,但是决定标题级别的是前面的 # 数量:

# 一级标题 #

## 二级标题 ##

### 三级标题 ###

#### 四级标题 ####

##### 五级标题 #####

###### 六级标题 ######

###### 还是六级标题 ##

在 Markdown 的标准中还有另外一种风格的标题表示方式,即在文本下一行用 = 表示一级标题、用 - 表示二级标题:

一级标题
========

二级标题
--------

标准中说“任何数量的 =- 都可以”,但是在一些实现中可能需要 2 个或以上才行。这种方式只能表示一级标题和二级标题,所以为了统一,我本人不建议使用这种方式。

引用块

引用块表示的是引用的一段信息,对应的是 HTML 中的 <blockquote></blockquote> 。引用块中可以是任何内容,只要按照内容原本的格式,在每一行开头加上 > (大于号 + 空格)即可。类似这样:

> 引用块中的普通段落
>
> > 嵌套的引用块
>
> 第三个段落  
> 段落内换行
>
> 1. 引用块中的有序列表
> 2. 有序列表

上面这段示例效果如下:

引用块中的普通段落

嵌套的引用块

第三个段落
段落内换行

  1. 引用块中的有序列表
  2. 有序列表

如果引用的是一个段落,你也可以只在段落第一行加 >

> 一个偷懒的引用块  
段落内换行

但是为了可读性,我本人不建议使用偷懒的方法。

列表

列表包含有序列表和无序列表,分别对应 HTML 中的 <ol></ol><ul></ul>

有序列表的每一项是一行以 <数字>. (数字 + 英文句号 + 空格)开头的文本,类似这样:

1. 第一项
2. 第二项
3. 第三项

效果如下:

  1. 第一项
  2. 第二项
  3. 第三项

了解 HTML 的同学应该知道,列表的每一项是用 <li></li> 标记的,数字没有标注出来,而是由位置决定。所以 Markdown 有序列表每一行开头的数字也仅仅是给人看的,它可以是任意的,并不影响最终显示效果。比如上面的示例用下面这些写法也是完全等效的:

1. 第一项
1. 第二项
1. 第三项

3. 第一项
2. 第二项
1. 第三项

101. 第一项
200. 第二项
13. 第三项

但是为了可读性,我建议按照实际位置标注序号。而且在一些实现中,有序列表的序号会从第一个序号开始递增(而不是 1 ),比如 Hexo ,所以最好还是不要乱标序号。

无序列表的每一项是一行以 <*、-、+> (星号、减号或加号 + 空格)开头的文本,类似这样:

- 吃饭
- 玩游戏
- 睡觉

效果如下:

  • 吃饭
  • 玩游戏
  • 睡觉

无序列表每一行开头的符号可以是 -+* ,它们完全等效。虽然标准中没有规定不能在同一个无序列表中混用这三种符号,但是已知一些实现是不支持混用的。

列表中的每一项都可以支持其它元素,也可以分段、可以换行。如果要这样做,每一个块元素都必须用空格缩进到与列表项对齐的位置:

1. 第一项的第一个段落

   第一项的第二个段落  
   第一项第二个段落的第二行
2. > 第二项是一个引用块  
   > 引用块的第二行
3. 第三项。如果是一个段落,除了段落的第一行,其它几行可以不对齐  
比如这样

效果如下:

  1. 第一项的第一个段落

    第一项的第二个段落
    第一项第二个段落的第二行

  2. 第二项是一个引用块
    引用块的第二行

  3. 第三项。如果是一个段落,除了段落的第一行,其它几行可以不对齐
    比如这样

同样的,列表项中可以包含列表:

1. 第一项
   1. 嵌套有序列表的第一项
   2. 嵌套有序列表的第二项
2. 第二项
   - 也能嵌套无序列表
     - 还能继续嵌套
   - 嵌套无序列表的第二项

效果如下:

  1. 第一项
    1. 嵌套有序列表的第一项
    2. 嵌套有序列表的第二项
  2. 第二项
    • 也能嵌套无序列表
      • 还能继续嵌套
    • 嵌套无序列表的第二项

可以看到,嵌套的列表标号、符号可能会变化。

代码块

有时候,你需要在你的文章中展示一段代码

比如你新学会的“Hello World!”

#include <stdio.h>

int main()
{
	printf("Hello Worle!\n");
	return 0;
}

你可以用两种方式表示一个代码块,一个是每行缩进 4 个空格或一个制表符,比如上面那段 C 程序可以这样写:

#include <stdio.h>

int main()
{
	printf("Hello Worle!\n");
 return 0;
}

需要注意的是,代码块中的 HTML 和 Markdown 语法都是不起效的,它们只会以“原始文本”的样子呈现。

虽然标准没有提到,但是几乎所有实现都支持第二种表示方式,即使用一对 ``` (三个反引号)号包围:

#include <stdio.h>

int main()
{
	printf("Hello Worle!\n");
 return 0;
}

它们都是等效的,会被转换为这样的 HTML 代码,即代码块内的内容被置于 <pre><code></code></pre> 中。

在大多数实现中你可以顺便指定代码块中代码的类型,比如:

print('Hello World!')

从而得到针对性的语法高亮效果。

但无论是三反引号标志还是语言类型的标注,在标准中都没有提及,这些只是已有的实现中广泛存在的。而且具体支持哪些语言以及这些语言的表示方法(比如 py3 还是 python3 或是 python )也是取决于实现的,而且充满了混乱(特别是基于 JS 的实现,比如 Hexo 所使用的一些插件)。

但是我本人还是更喜欢使用三反引号标志,因为这样比较易读和易写。

水平分割线

除了段落内换行、分段, Markdown 还支持一种效果更强的内容分隔方式,即水平分割线,对应 HTML 中的 <hr> 。只要一行中仅含 3 个或 3 个以上的 -* (减号或星号),那么它就是一个分割线(可以包含一些间隔的空格):

---

***

- - -------- - - ---

以上代码效果都是一样的,就像下面这样:


一条横贯文章的分割线。

从我本人的实践来看,这种方式呈现的分隔效果过于强,不适合大量使用,一般只用在文章中分隔内容完全不衔接的两部分(比如附录和正文)。

任务列表

这不是标准的一部分,也不是“标准级”的拓展语法。但是挺有用,而且被已有的实现广泛支持(有些需要安装插件或者配置开启)。他的语法类似这样:

- [x] 这表示完成
- [ ] 这表示未完成

效果如下:

  • 这表示完成
  • 这表示未完成

表格

令我很意外的是,表格并没有在标准中被提及,但它是被几乎所有实现支持的语法。

Markdown 中的表格类似这样:

| Header1 | Header2 | Header3 | Header4 |
| ------- | :------ | :-----: | ------: |
| (1,1)   | (2,1)   | (3,1)   | (4,1)   |
| (1,2)   | (2,2)   | (3,2)   | (4,2)   |
| (1,3)   | (2,3)   | (3,3)   | (4,3)   |

列与列之间用 | (竖线)分隔。第一行是表头,第二行由 -: (减号和冒号)构成,冒号的位置决定了是左对齐、右对齐还是居中对齐(没有冒号表示默认对齐方式)。然后后面每一行就是表格中的一行。通过补空格排列成这种规整的格式并不是必须的,这只是为了源码易于阅读,但是 | 两边的空格一般是必须的。

以上示例代码的效果如下:

Header1 Header2 Header3 Header4
(1,1) (2,1) (3,1) (4,1)
(1,2) (2,2) (3,2) (4,2)
(1,3) (2,3) (3,3) (4,3)

这只能用于制作一些十分简单的表格,它不支持单元格合并,也不能让表头在第一列,或者双表头。

其它扩展

除了上面提到的标准语法和近于标准待遇的语法外,还有很多扩展语法。

数学公式块也是一个常用的扩展,但是它的实现情况并不乐观,没有被多数实现所支持,即使支持一般也需要通过插件,而且不同的实现在使用和表现上不完全相同。它一般用一对 $$ 符号包围数学方程表达式,表达式的语法类似于 LaTeX 。我不想给我的 Hexo 加上这种插件,所以不演示了,感兴趣的同学可以自己查询自己所使用的实现是否支持以及如何支持,然后自行尝试。

在不多的一些实现中可以使用 [TOC] 表示一个目录。

在一些实现中可以以类似引用块的方式展示一些提示信息,而且用颜色表示不同的级别( info 、 tips 、 warning 、 fail 、 success 、 … )

行内元素

行内元素对应的是 HTML 中默认 display: inline 的元素。它们能作用于任何字符串,嵌入在段落或其它任何块元素中。

超链接

对应 HTML 的 <a></a> 元素。Markdown 支持三种类型的超链接:内联式、引用式和自动链接。

内联式 类似这样:

[内联式链接一](https://blog.keybrl.com/ "链接的标题,这是可以选填的,一般在鼠标悬浮于链接之上时可见。")  
[内联式链接二](https://blog.keybrl.com/)

效果如下:

内联式链接一
内联式链接二

(链接的 URL 和链接的标题之间有一个空格。)

一般来说仅使用内联式链接已经足够了,这也是多数人常用的超链接写法。但是如果你需要反复写同一个超链接,或者你链接的 URL 过于冗长,可能会降低可读性。所以 Markdown 还支持链接文本和 URL 分离的 引用式 写法。

[引用式链接][link1] 在段落中具有更好的 [可读性][link2] ,因为不会有一个超长的 URL 隔断在一句话的中间,而且同一个链接可以被使用 [第二次][link1] 。

[link1]: https://blog.keybrl.com/
[link2]: https://blog.keybrl.com/ "引用式链接也是可以加标题的"

引用式链接包括连接的声明和定义两部分,声明格式: [链接文本][链接名] 。声明时可以随便用一个字符串替代链接 URL 和标题(比如上面例子中的 link1 )。然后定义以 [链接名]: URL "连接标题" 的格式,链接定义可以被放置在文章的任何地方,你可以就近放置在一个段落或者一个小节后面,也可以放置在文章最后。

效果如下:

引用式链接 在段落中具有更好的 可读性 ,因为不会有一个超长的 URL 隔断在一句话的中间,而且同一个链接可以被使用 第二次

有时候你需要让链接文本是链接的 URL 本身,也就是 自动链接 ,那么你可以用尖括号包围 URL ,类似这样:

<https://blog.keybrl.com/>

它的效果就像这样: https://blog.keybrl.com/

但是如果你在自动链接中填的 URL 是一个 E-Mail 地址:

<keyboard-l@outlook.com>

它的效果是这样的: keyboard-l@outlook.com ,看起来没什么不同,但其实它的实际链接地址是 mailto:keyboard-l@outlook.com ,这样在点击后一般会自动打开系统默认的发邮件应用给我发邮件。而且在标准中,这个 E-Mail 的链接实际上被加密为了这样:

<a href="mailto:&#107;&#x65;&#121;&#98;&#x6f;&#x61;&#114;&#x64;&#45;&#x6c;&#64;&#x6f;&#117;&#116;&#108;&#111;&#x6f;&#107;&#x2e;&#99;&#111;&#x6d;" target="_blank" rel="noopener">&#107;&#x65;&#121;&#98;&#x6f;&#x61;&#114;&#x64;&#45;&#x6c;&#64;&#x6f;&#117;&#116;&#108;&#111;&#x6f;&#107;&#x2e;&#99;&#111;&#x6d;</a>

只不过浏览器给你转为了人类可读的样子。这样可以一定程度上避免你的邮箱地址被一些拙劣的网络爬虫获取。

不管是什么形式的链接,链接 URL 可以是绝对地址或者相对地址,或者锚点,或者随便什么,反正你填的 URL 最终就是被填进 <a><\a> 标签的 href 属性中。

强调

在 Markdown 中,强调是用 *_ (星号和下划线)包围的文字表示的。比如这样:

**两个星号表示全局强调** ,或者也可以使用 __两个下划线__ 。  
类似的 *一个星号*_一个下划线_ 表示局部强调。  
需要注意的是强调内部紧邻 ** 星号或下划线的地方不能有空格** ,否则它们只会呈现出普通星号或下划线。

效果如下:

两个星号表示全局强调 ,或者也可以使用 两个下划线
类似的 一个星号一个下划线 表示局部强调。
需要注意的是强调内部紧邻 ** 星号或下划线的地方不能有空格** ,否则它们只会呈现出普通星号或下划线。

全局强调对应的是 HTML 的 <strong></strong> (很多人误以为是 <b></b> ,这是不对的),局部强调对应 HTML 是 <em></em>

它们在表现上一般分别是 加粗斜体

千万不要在文章中大量使用强调,我过去也很喜欢滥用强调,因为我觉得就随便加粗一下看起来比较棒。但是后来我看到这样一个说法:

局部强调是阅读到附近时才能发现的,而全局强调是在篇章的尺度上能被明显区分的。

一想还真是这样,所以如果有大量全局强调,整篇文章给人的视觉冲击会过大,导致疲劳。

行内代码

有时候你只需要在段落内表示一小段代码,比如 "Hello World!\n" ,而不是独立的代码块,那么你只需要像下面这样用一对反引号包围代码:

`"Hello World!\n"`

这对应 HTML 中的 <code></code> 元素。

如果你想在行内代码中包含 ` (反引号)。那么你需要使用一对双反引号包括代码。比如刚刚展示的那个反引号是这样写的:

`` ` ``

同样,在行内代码中的 Markdown 和 HTML 语法不会产生作用。

图片

图片对应的是 HTML 中的 <img> 。同超链接一样,图片也支持内联式和引用式。实际上图片和超链接的语法差别仅仅是图片以一个 ! (感叹号)开头:

![示例图片](https://blog-assets-1253422097.file.myqcloud.com/images/2016-11-13-the-base-of-grammar-in-markdown/insect.jpg "图片标题,选填的,鼠标悬浮时可见")  
![图片加载失败时会显示这段文字,这其实也是选填的](https://www.hhh.com/xxx.jpg)  
![引用式图片示例][img1]

[img1]: https://blog-assets-1253422097.file.myqcloud.com/images/2016-11-13-the-base-of-grammar-in-markdown/insect.jpg "图片标题,选填的,鼠标悬浮时可见"

语法与超链接几乎一样,只不过以 ! 开头,而且 URL 是指图片 URL 。

以上示例效果如下:

示例图片
图片加载失败时可能会显示这段文字,这其实也是选填的
引用式图片示例

同超链接一样,图片链接也可以是相对地址,只要相对 Markdown 的展示环境的相对地址中图片存在即可,反正它仅仅被简单地填在 <img> 标签的 src 属性中。

其它扩展

同块元素一样,行内元素也有很多扩展。

同代码有代码块和行内代码一样,数学公式除了有数学公式块,也有行内数学公式。它一般用一对 $ (美元符号)包围。同数学公式块一样,这个扩展的实现充满了混乱。

在一些实现中会有 划掉 或者 下划线 的语法(别误会,我这个示例不是用这个语法做的),它们可能会使用 ~ (波浪线)表示,具体我忘了。

有一些扩展还具有类似荧光马克笔涂过之后的高亮效果(而且有红、绿、黄等颜色)。

脚注也是一个很有用的扩展。脚注允许你在文章中给一些词右上角标注数字,然后在文章最后对这些标注做出更详细的解释。并且一般会添加一些锚点,以支持文本直接跳转到脚注,点击脚注跳回文本的特性。这在写一些比较学术的文章时比较有用。

兼容 HTML

Markdown 是一种适用于网络的书写语言。

Markdown 可以说是一种 HTML 的生成工具,所以 Markdown 没有什么理由不兼容支持 HTML 的语法。

Markdown 中出现的所有 HTML 标签都会被直接转换为一摸一样的 HTML 代码。比如我前面演示的 下划线 就是用 <u>下划线</u> 实现的。

像 Markdown 一样,有些 HTML 标签是 行内元素 ,有些则是属于 块元素 ,应用时要注意

在 HTML 标签内,Markdown 语法不会发挥作用。

特殊字符自动转换

在 HTML 中 &< 是两个令人备受折磨的字符,因为他们都在 HTML 中作为语法的一部分有特殊含义。因此当你需要输入 &< 时,你需要输入 &amp;&lt; ,甚至一些展示出来的 URL 中有 & 时也要转换。

幸运的是,一般情况下,在 Markdown 中你并不需要这样,你只需要直接打出 &< 即可,但是 …

Every coin has two sides.

这在 Markdown 中有点复杂。当你将它们当作一般的字符书写出来,Markdown 会自动将它们转换为 &amp;&lt; 。但前面说过,Markdown 支持 HTML ,因此,当 Markdown “认为”那是 HTML 语法时,Markdown 不会转换他们。

如果你想在文中加入一个版权符号 &copy; ,你输入:

&copy;

Markdown 并不会转换他们,而是原样输入 HTML (即显示为 &copy; )。但如果你输入:

1 + 1 < 5

Markdown 会自动将他们转换为:

1 + 1 &lt; 5

(即显示为 1 + 1 < 5 )

因此了解 Markdown 何时自动转换、何时不转换十分重要。

反斜杠转义

Markdown 可以利用反斜杠来插入一些在语法中有其它意义的符号,比如如果你想在文字旁边加入一些真的 *星号* ,你可以在星号的前面加上反斜杠:

\*星号\*

Markdown 支持在以下这些符号前面加上反斜杠来帮助插入普通的符号:

\   反斜线
`   反引号
*   星号
_   下划线
{}  花括号
[]  方括号
()  圆括号
#   井字号
+   加号
-   减号
.   英文句点
!   英文惊叹号

结语

多看多想
        ——某dalao

上面两行是我在 2016 年最初的文章中写下的,实际上我现在觉得这跟这篇文章没啥关系,本来想删掉。但是考虑到这篇文章几乎每一个字都在 2020 年的重写中被覆盖了。考虑到这毕竟是我博客第一篇文章,还是留下一点点回忆比较好。

SS::STA 2016 年(那时我大一)的前几次活动中的某一次,一个 dalao 给我们分享了如何通过 GitHub Pages 部署一个 Jekyll 构建的博客,该博客的第一版就是这样弄起来的(虽然后面经过了不知道多少次主题、部署方式、构建方案的迁移才成为现在这样)。那位 dalao 当时是对着一份他用 Markdown 编写的“教案”给我们讲,那份教案最后就有这样一句话,“多看多想”。