正则表达式
致读者:如果你是初学者,完全不懂正则,那么我推荐你先阅读这篇文章。
案例解决方案
vscode 中正则匹配英文单词左右没有空格的单词
1
[^ \n\w\[\(\/\.\-\?\* %"<=`+:;#{'。,、(:“][a-zA-Z]+
解释:要求 [a-zA-Z]+ 前面不能是以下字符:
\n
行首\w
,\(
,\/
,\[
,\.
,\-
,\?
,\*
- ` ` 空格
%
,"
,<
,=
,+
,:
,;
,#
,{
,'
,`
。
,,
,、
,(
,:
,“
1
2
[^ \*]\*{1,3}\w+
考虑到某些单词会被加粗或设置为斜体,所以还需要搜索一下这类单词前面没有空格的情况。
字符串转换为数组:每两个字符作为一个元素
1
2
console.log('1234567'.match(/.{1,2}/g))
// [ '12', '34', '56', '7' ]
^
和 (?<=)
一起使用
1
2
3
4
const str = 'hello, world'
console.log(str.match(/^(?<=hello, )(?<n>.*)$/)) // null
console.log(str.match(/(?<=^hello, )(?<n>.*)$/)) // 有输出
// 可见,^ 应该放在 (?<=) 里面
获取 //
注释后的内容
1
2
3
4
5
6
const str = `
// Controls whether the diff editor shows empty decorations to see where characters got inserted or deleted.
// Controls whether the diff editor should show detected code moves.
`
const re = new RegExp('(?<=//)(?<comment>.*)', 'g')
console.log(str.match(re))
基础知识
标志(flags)
flags | 对应的 RegExp 属性 | 描述 |
---|---|---|
g | global | 全局匹配 |
i | ignoreCase | 忽略大小写 |
s | dotAll | 允许 . 匹配行终结符(回车换行符) |
m | multiline | 允许多行搜索,即 ^ 和 $ 将跨行 |
d | hasIndices | 允许匹配子串时生成索引 |
u | unicode | 允许使用 unicode 码的模式进行匹配 |
v | unicodeSets | 允许使用 unicode 属性转义集合 |
y | sticky | 允许粘性搜索,从目标字符串的当前位置开始匹配 |
RegExp 对象
一种有两种方式可以创建正则对象:
1
2
3
4
5
6
7
8
// 字面量方式创建。好处:快捷方便、无需对 \ 转义
const re = /ab+c/i
const re2 = /\*/
// new 对象方式。好处:可以使用变量,但需要对 \ 转义
const reg = new RegExp('ab+c', 'i')
const reg = new RegExp('\\*')
被废弃的静态属性/构造函数属性:
input
($_
) 最后搜索的字符串lastMatch
($&
) 最后匹配的字符串lastParen
($+
) 最后匹配的捕获组leftContext
($`
) input 字符串中出现在 lastMatch 前面的文本rightContext
($'
) input 字符串中出现在 lastMatch 后面的文本
实例属性
实例属性 | 说明 |
---|---|
ignoreCase | 判断是否使用了 i 标志 |
global | 判断是否使用了 g 标志 |
dotAll | 判断是否使用了 s 标志 |
multiline | 判断是否使用了 m 标志 |
unicode | 判断是否使用了 u 标志 |
sticky | 判断是否使用了 y 标志 |
hasIndices | 判断是否使用了 d 标志 |
unicodeSets | 判断是否使用了 v 标志 |
flags | 返回一个由该对象使用的所有标志组成的字符串 |
source | 获取正则匹配模版 |
lastIndex | 需开启 g 或 y 标志才有效。用于指定下一次匹配的起始索引 |
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log((/fooBar/gi).source)
// 输出 fooBar
console.log(new RegExp().source);
// 输出 (?:)
console.log((/\n/).source);
// 输出 \n
console.log((/\n/).source === '\\n');
// 输出 true (starting with ES5)
// Due to escaping
console.log((/\\n/).source);
// 输出 \\n
console.log((/\\n/).source === '\\\\n')
// 输出 true
实例方法
简单案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 注意要开启 g,不然每次 exec 时不会更新 lastIndex,这样会导致后面的 while 变成死循环
const reg = RegExp('foo[^b]', 'g')
const str = 'table football, foosball'
let result
while ((result = reg.exec(str)) !== null) {
console.log(`匹配到 ${result[0]} 下标位置是 ${result.index} 下一次将从下标 ${reg.lastIndex} 继续匹配`)
// 匹配到 foot 下标位置是 6 下一次将从下标 10 继续匹配
// 匹配到 foos 下标位置是 16 下一次将从下标 20 继续匹配
}
const str2 = 'foobar'
console.log(reg.test(str2))
// false
内部方法
内部方法 | 说明 |
---|---|
@@split | |
@@match | |
@@matchAll | 需开启 g 标志 |
@@search | |
@@replace |
String 对象实现了上面五种内部方式。
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const str = 'date: 2024-01_02'
console.log(str.split(/-|_/))
// [ 'date: 2024', '01', '02' ]
console.log(str.match(/\d+/))
// [ '2024', index: 6, input: 'date: 2024-01_02', groups: undefined ]
for (const result of str.matchAll(/\d+/g)) {
console.log(result)
// [ '2024', index: 6, input: 'date: 2024-01_02', groups: undefined ]
// [ '01', index: 11, input: 'date: 2024-01_02', groups: undefined ]
// [ '02', index: 14, input: 'date: 2024-01_02', groups: undefined ]
}
console.log(str.search(/\d/))
// 6
console.log(str.replace(/-|_/, ','))
// date: 2024,01_02
console.log(str.replace(/-|_/g, ','))
// date: 2024,01,02
断言
输入内容边界断言 ^
$
^
表示输入内容的开头;$
表示输入内容的末尾
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const str1 = 'hello, world'
console.log(/lo, wor/.test(str1))
// true
console.log(/^lo, wor$/.test(str1))
// false
const str2 = `hello
world`
console.log(/^hello\r?\nworld$/.test(str2))
// true 注意,如果是 win 系统的换行符是 \r\n
console.log(/^hello/.test(str2))
// true
console.log(/world$/.test(str2))
// true
单词边界断言 \b
\B
什么是单词边界?当我们通过快捷键 ctrl+left
和 ctrl+right
进行跳转时,其实就是跳转到下一个单词边界。或者可以在 vscode 中进行正则搜索 \b
,这样可以很直观地看到效果。
默认情况下,中文(包括标点)不会被认为是一个一个单词(word)。不过,如果想要对中文词进行分隔,可以借助查阅 Segmenter
简单案例:
1
2
3
4
5
6
7
8
9
const str = "To be, or not to be, that is the question."
console.log(/no/.test(str))
// true
console.log(/\bno\b/.test(str))
// false
先行断言 foo(?=bar)
foo(?!bar)
foo(?=bar)
要求 foo 后有 bar 时才匹配 foo
foo(?!bar)
要求 foo 后不存在有 bar 时才匹配 foo
简单案例:
1
2
3
4
5
6
7
8
9
10
const str = 'hello, world. hello.'
console.log(/hello/.exec(str))
// [ 'hello', index: 0, ]
console.log(/hello(?=\.)/.exec(str))
// [ 'hello', index: 14, ]
console.log(/hello(?!,)/.exec(str))
// [ 'hello', index: 14, ]
后行断言 (?<=bar)foo
(?<!bar)foo
(?<=bar)foo
要求 foo 前有 bar 时才匹配 foo
(?<!bar)foo
要求 foo 前不存在有 bar 时才匹配 foo
简单案例:
1
2
3
4
5
6
7
8
9
10
const str = "All the world's a stage, and all the men and women merely players."
console.log(/men/.exec(str))
// [ 'men', index: 37, ]
console.log(/(?<=wo)men/.exec(str))
// [ 'men', index: 47, ]
console.log(/(?<!wo)men/.exec(str))
// [ 'men', index: 37, ]
量词
语法 | 说明 |
---|---|
? | 0 次或 1 次 (非贪婪匹配) |
* | 0 次或 0 次以上 |
+ | 1 次或 1 次以上 |
{n} | 刚好 n 次 |
{n,} | n 次或 n 次以上 |
{n,m} | n 到 m 次(包含 n 和 m) |
注意,使用花括号时中间不能有空格!
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
console.log("foooooo bar".match(/o+/)[0])
// 贪婪匹配: oooooo
console.log("foooooo bar".match(/o+?/)[0])
// 非贪婪匹配: o
console.log("bar abb abbb foo".match(/b{2}/g))
// [ 'bb', 'bb' ]
console.log("bar abb abbb foo".match(/b{2,}/g))
// [ 'bb', 'bbb' ]
console.log(/a{1, 3}/.test('aaaa'))
// false 当花括号中出现空格时,会花括号被解析成字面量,而不是量词。
console.log(/a{1, 3}/.test('a{1, 3}'))
// true 虽然这样可以成功运行,但并不推荐!这种特性是为了兼容历史遗留问题
console.log(/a\{1, 3\}/.test('a{1, 3}'))
// true 使用转义时一种更规范的用法
console.log('😀😀😀'.match(
/😀{2,3}/u
))
// 在 u 模式下如果在花括号中添加空格,会直接报错!
字符类
元字符
正则表达式中有以下元字符(metacharacter):
1
. * ? + ^ $ \ | ( ) [ ] { }
想要匹配元字符时需要通过 \
进行转义。某些情况下也可以直接包裹在 []
中。除了 [\^]
, [\\]
, [\]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/a[.]b/.test('a.b') // true
/a[*]b/.test('a*b') // true
/a[?]b/.test('a?b') // true
/a[+]b/.test('a+b') // true
/a[^]b/.test('a^b') // true
/a[$]b/.test('a$b') // true
/a[|]b/.test('a|b') // true
/a[(]b/.test('a(b') // true
/a[)]b/.test('a)b') // true
/a[{]b/.test('a{b') // true
/a[}]b/.test('a}b') // true
/a[[]b/.test('a[b') // true
/a[\^]b/.test('a^b') // true
/a[\\]b/.test('a\\b') // true
/a[\]]b/.test('a]b') // true
通配符 .
.
默认不匹配行终结符符(换行),除非开启 s
标志。
一个 Unicode 字符只会消耗一个 .
win 系统中换行符是 \r\n
,而 .
只能匹配一个 \r
或者一个 \n
。
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
console.log(/hello.world/.test('hello\tworld'))
// true
console.log(/hello./.test('hello\nworld'))
// false 不开启 s 标志时,通配符不支持换行符
console.log(/hello.world/s.test('hello\r\nworld'))
// false
// 一个 . 只能匹配一个 \r 或一个 \n
console.log(/hello..world/s.test('hello\r\nworld'))
// true
字符转义
\f 换页 \n 换行 \r 回车 |
\t 水平制表符 \v 垂直制表符 |
\0 字符 NUL |
\( 、\) |
\[ 、\] |
{ 、} |
\^ 、\$ |
\\ 、\. 、\* 、\+ 、\? |
\| 、\/ |
\cA , \cB , … 脱字符表示法,表示 ASCII 控制字符 |
\xHH 必须 2 个十六进制位,比如 \x20 匹配空格 |
\uHHHH 必须 4 个十六进制位,比如 \uFFE5 匹配 ¥ |
\u{HHHHHH} 需先开启 u 标志,允许 1 到 6 个十六进制位,比如 \u{1F600} 匹配 😀 |
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
console.log(/\x20/.test(' '))
// true
console.log(/\u20/.test(' '))
// false 必需四位 16 进制
console.log(/\u0020/.test(' '))
// true 必需四位 16 进制
console.log(/\uFFE5/.test('¥'))
// true
console.log(/\u{1F600}/.test('😀'))
// false 未开启 u 标志
console.log(/\u{1F600}/u.test('😀'))
// true
console.log('\x01Hello World!'.match(/^\cA/))
// 匹配以控制字符 SOH 开头的字符串
console.log('Hello World!\x06'.match(/\cF$/))
// 匹配以控制字符 ACK 结尾的字符串
字符类转义 \d
\w
\s
转义字符 | 说明 |
---|---|
\d | (digit) 数字 |
\D | 非数字 |
\w | (word) 数字 字母 下划线 |
\W | 非数字 字母 下划线 |
\s | (space) 空格、制表符(\t )、换行符(\r \n ) |
\S | 非空格、制表符(\t )、换行符(\r \n ) |
\s
实际上表示的是空白字符和行终结符。 完整的空白字符包含\t
\v
\f
U+0020
U+00A0
U+FEFF
和其他 unicode 空白符; 完整的行终结符包含\n
\r
U+2028
U+2029
四个。 详见 词法文法
简单案例:
1
2
3
4
5
const str = `Look at the stars
Look how they \t\t\tshine for you`
console.log(str.split(/\s+/))
// [ 'Look', 'at', 'the', 'stars', 'Look', 'how', 'they', 'shine', 'for', 'you' ]
Unicode 字符类转义 \p{...}
\P{...}
使用时需要开启 u
标志。
常见的 unicode 属性名 有:L(Letter)、N(Number)、Emoji 等等。比如 \p{Emoji}
能实现只匹配 Emoji 符号。/[\u4E00-\u9FFF]/ug
能匹配大多数中文。
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const str1 = '✨你好❗'
console.log(str1.match(
/\p{Emoji}/u
)[0])
// [ '✨' ]
console.log(str1.match(
/\p{Emoji}/ug
))
// [ '✨', '❗' ]
const str2 = '魑魅魍魉 魃鬾魑魊 魖魈鬽魁 魓魌鬿魕 魆魒魐魖魀'
console.log(str2.match(
/\p{L}+/ug
))
// [ '魑魅魍魉', '魃鬾魑魊', '魖魈鬽魁', '魓魌鬿魕', '魆魒魐魖魀' ]
const str3 = '你好,鿾 鿿 ꀀ ꀁ'
console.log(str3.match(
/[\u4E00-\u9FFF]/ug
))
// [ '你', '好', '鿾', '鿿' ]
const str = '你到底知道不知道?愛是什麽'
const granularities = ['grapheme', 'word', 'sentence']
granularities.forEach(granularity => {
console.table(Array.from((new Intl.Segmenter('zh', { granularity })).segment(str)))
// grapheme
// '你', '到', '底', '知', '道', '不', '知', '道', '?', '愛', '是', '什', '麽'
// word
// '你', '到底', '知道', '不知道', '?' , '愛', '是', '什', '麽'
// sentence
// '你到底知道不知道?', '愛是什麽'
})
析取字符 |
相当于逻辑或。它在正则表达式中的优先级是最低的。
简单案例:
1
2
3
4
5
6
7
8
9
10
console.log('abc'.match(
/a|ab/
)[0])
// a
console.log('abbacc'.match(
/a(c|b)/
)[0])
// ab
捕获组 ()
和后向引用 \1
后向引用 \1
一个很常见的需求就是匹配连续多个字符相同的字符,比如 abb 模式, aabb 模式, abba 模式等等
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log('aab bba'.match(
/(.)\1/g
))
// [ 'aa', 'bb' ]
console.log('aabb ccdd'.match(
/(.)\1(.)\2/g
))
// [ 'aabb', 'ccdd' ]
console.log('abba cddc'.match(
/(.)(.)\2\1/g
))
// [ 'abba', 'cddc' ]
在 vscode 中的替换时,在替换栏中的
$1
,$2
和这里的后向引用时一个的道理。比如: 搜索栏使用正则:\[(\s)\]
; 替换栏使用$1
,这样就可以批量删除配对的中括号。
指定字符的范围 [..]
[^...]
注意, [^...]
是一体的,否定字符 ^ 不能单独使用。比如 [a^b]
中的 ^
仅仅代表一个字符,没有否定的含义
简单案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
console.log('log, lig, lag, lug'.match(
/l[aio]g/g
))
// [ 'log', 'lig', 'lag' ]
console.log('log, lig, lag, lug'.match(
/l[^aio]g/g
))
// [ 'lug' ]
console.log('log, lIg, lAg, lug, loon'.match(
/l[a-zA-Z]+[gn]/g
))
// [ 'log', 'lIg', 'lAg', 'lug', 'loon' ]
console.log('2^3=8; 3*2=6; 8+6=9'.match(
/\d[*^]\d/g
))
// [ '2^3', '3*2' ]
console.log('2[3=8; 3]2=6; 8+6=9'.match(
/\d[\[\]]\d/g
))
// [ '2[3', '3]2' ]
捕获组 ()
捕获组,其实就是嵌套的概念。后向引用就是引用捕获组中的内容!
捕获组的匹配信息可以通过以下方式访问:
- RegExp.prototype.exec()、String.prototype.match() 和 String.prototype.matchAll() 的返回值(一个数组)
- String.prototype.replace 和 String.prototype.replaceAll 中的
$1
,$2
, … 参数 - 反向引用 中的
\1
,\2
, …
举例说明 (1):
1
/(ab)|(cd)/.exec("cd") // ['cd', undefined, 'cd']
上面代码中定义了来个捕获组(两个括号),返回了一个包含三个元素的数组(忽略其属性值)。其中下标为 0 的元素并不是捕获组的内容,它表示的是整个正则的匹配结果。而下标为 1 和 2 的元素则分别对应第 1 和第 2 个捕获组。由于第一个捕获组((ab)
)并没有匹配的内容,所以其值为 undefined
。
举例说明 (2):
1
2
/([ab])+/.exec("abc") // ['ab', 'b']
/([ab])+/.exec("bac") // ['ab', 'a']
捕获组可以使用量词,上面案例中使用了 +
,在这种情况下,捕获组最终只会留下最后一次匹配的信息。上面例子中,使用正则 /([ab])+/
匹配 abc
时,捕获组依次捕获到 a
和 b
,所以只留下了 b
。
举例说明(3):
1
2
3
/((a+)?(b+)?(c))*/.exec("aac") // ['aac', 'aac', 'aa', undefined, 'c']
/((a+)?(b+)?(c))*/.exec("aacbbbc") // ['aacbbbc', 'bbbc', undefined, 'bbb', 'c']
/((a+)?(b+)?(c))*/.exec("aacbbbcac")// ['aacbbbcac','ac', 'a', undefined, 'c']
捕获组内可以继续嵌套捕获组。不管嵌套多少层,捕获组的编号顺序都是根据其左括号的出现顺序进行编号的。所以,上面代码中最外面的括号是第一个捕获组,然后里面的括号依次是 2,3,4 个捕获组。此外,如果捕获组使用了量词(比如上面代码中外部捕获组使用了 *
),则每当外部捕获组匹配到新的内容时,内部的捕获组的结果都会被覆盖。上面代码中专门写成了三行,就是因为外部捕获组成功匹配了三次:
- 第一次,识别到子串
aac
,此时符合外部捕获组的定义。其内部捕获组的结果分别是:aa
,undefined
,c
。如果此时外部捕获组没有使用量词,则匹配到此结束。但这里使用了量词*
,所以指针会继续匹配下一个子串。 - 第二次,成功匹配到子串
bbbc
,此时内部捕获组的结果分别是:undefine
,bbb
,c
。同理,由于使用了量词*
,所以指针会继续匹配 - 第三次,成功匹配到子串
ac
,此时内部捕获组的结果分别是:a
,undefined
,c
。
现在,看看下面的代码,你应该能够理解为什么了:
1
2
3
/((a+)?(b+)?(c))?/.exec("aacbbbcac") // ['aac', 'aac', 'aa', undefined, 'c']
/((a+)?(b+)?(c)){2}/.exec("aacbbbcac") // ['aacbbbc', 'bbbc', undefined, 'bbb', 'c']
/((a+)?(b+)?(c)){3}/.exec("aacbbbcac") // ['aacbbbcac', 'ac', 'a', undefined, 'c']
具名捕获组 (?<name>...)
见名知意,具名捕获组就是有名字的捕获组。具名捕获组的好处在于,我们可以直接通过名字来获取捕获组的内容,而不是通过下标。
简单案例:
1
2
3
4
5
6
7
const result = "2024-12-31".match(/(\d{4})-(\d{2})-(\d{2})/)
console.log(result[1], result[2], result[3])
// 2024 12 31
const groups = "2024-12-31".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)?.groups
console.log( groups.year, groups.month, groups.day )
// 2024 12 31
非捕获组 (?:...)
捕获组在运行时,会记住(存储)匹配到的内容,这会带来额外的性能消耗。而非捕获组则相反,它不会记住匹配到的内容。
什么时候可以考虑使用非捕获组呢?在正则表达式中,我们经常会用到括号 ()
,但大多数人使用括号时仅仅只是为了分组,而不是为了后面能够后向引用。也就是说,当我们只想分组,而不需要记住匹配到的内容时,就可以使用非捕获组!
简单案例:
1
2
3
4
5
function isStylesheet(path) {
return /styles(?:\.[\da-f]+)?\.css$/.test(path);
}
isStylesheet('styles.13ABF3.css') // true
案例 2:
1
2
3
4
5
6
7
8
9
function parseTitle(metaString) {
// 这里我们使用后向引用来更方便的匹配 value 值,而不需要判断字符串是使用什么括号进行包裹
return metaString.match(
/title=(["'`])(.*?)\1/
)[2]
}
parseTitle('title="foo"') // 'foo'
parseTitle("title=`foo`") // 'foo'
parseTitle("title='foo'") // 'foo'
上面代码可以完成任务,但后续我们可能想识别 key 等于 name 的情况,于是我们需要修代码:
1
2
3
4
5
6
// - 添加一个析取字符 |
// - 修改后向引用的数字为 \2
// - 修改数组的下标为 3
return metaString.match(
/(title|name)=(["'`])(.*?)\2/
)[3]
可以看到,其中的第一个括号,我们需要的功能仅仅只是分组,并不需要记住它的值,这个时候就可以使用非捕获组:
1
2
3
4
5
6
7
8
function parseTitleOrName(metaString) {
return metaString.match(
/(?:title|name)=(["'`])(.*?)\1/
)[2]
}
parseTitle( 'name="foo"') // foo
parseTitle("title=`bar`") // bar
parseTitle( "name='baz'") // baz
附录
脱字符标识符
脱字符表示法(Caret notation),是 ASCII 码中不可打印的控制字符的一种表示方式:用一个脱字符(^)后跟一个大写字符来表示一个控制字符的 ASCII 码值。
键盘上的 Ctrl 按键其实是 control characters 中的 control,许多系统的终端都使用 Ctrl 按键加键盘上一个另一个按键来输入控制字符。
ASCII 码中不可打印的控制字符共有 33 个:0x00 - 0x1F
加上 0x7F
。
binary | decimal | hex | abbr. | Unicode | Caret notation | 名称/意义 |
---|---|---|---|---|---|---|
0000 0000 | 0 | 0x00 | NUL | ␀ | ^@ | 空字符(Null) |
0000 0001 | 1 | 0x01 | SOH | ␁ | ^A | 标题开始 |
0000 0010 | 2 | 0x02 | STX | ␂ | ^B | 本文开始 |
0000 0011 | 3 | 0x03 | ETX | ␃ | ^C | 本文结束 |
0000 0100 | 4 | 0x04 | EOT | ␄ | ^D | 传输结束 |
0000 0101 | 5 | 0x05 | ENQ | ␅ | ^E | 请求 |
0000 0110 | 6 | 0x06 | ACK | ␆ | ^F | 确认回应 |
0000 0111 | 7 | 0x07 | BEL | ␇ | ^G | 响铃 |
0000 1000 | 8 | 0x08 | BS | ␈ | ^H | 退格 |
0000 1001 | 9 | 0x09 | HT | ␉ | ^I | 水平定位符号 |
0000 1010 | 10 | 0x0A | LF | ␊ | ^J | 换行键 |
0000 1011 | 11 | 0x0B | VT | ␋ | ^K | 垂直定位符号 |
0000 1100 | 12 | 0x0C | FF | ␌ | ^L | 换页键 |
0000 1101 | 13 | 0x0D | CR | ␍ | ^M | Enter键 |
0000 1110 | 14 | 0x0E | SO | ␎ | ^N | 取消变换(Shift out) |
0000 1111 | 15 | 0x0F | SI | ␏ | ^O | 启用变换(Shift in) |
0001 0000 | 16 | 0x10 | DLE | ␐ | ^P | 跳出数据通讯 |
0001 0001 | 17 | 0x11 | DC1 | ␑ | ^Q | 设备控制一(XON 启用软件速度控制) |
0001 0010 | 18 | 0x12 | DC2 | ␒ | ^R | 设备控制二 |
0001 0011 | 19 | 0x13 | DC3 | ␓ | ^S | 设备控制三(XOFF 停用软件速度控制) |
0001 0100 | 20 | 0x14 | DC4 | ␔ | ^T | 设备控制四 |
0001 0101 | 21 | 0x15 | NAK | ␕ | ^U | 确认失败回应 |
0001 0110 | 22 | 0x16 | SYN | ␖ | ^V | 同步用暂停 |
0001 0111 | 23 | 0x17 | ETB | ␗ | ^W | 区块传输结束 |
0001 1000 | 24 | 0x18 | CAN | ␘ | ^X | 取消 |
0001 1001 | 25 | 0x19 | EM | ␙ | ^Y | 连线介质中断 |
0001 1010 | 26 | 0x1A | SUB | ␚ | ^Z | 替换 |
0001 1011 | 27 | 0x1B | ESC | ␛ | ^[ | 退出键 |
0001 1100 | 28 | 0x1C | FS | ␜ | ^\ | 文件分割符 |
0001 1101 | 29 | 0x1D | GS | ␝ | ^] | 群组分隔符 |
0001 1110 | 30 | 0x1E | RS | ␞ | ^^ | 记录分隔符 |
0001 1111 | 31 | 0x1F | US | ␟ | ^_ | 单元分隔符 |
0111 1111 | 127 | 0x7F | DEL | ␡ | ^? | 删除 |