sed 正则表达式
正则表达式是非常强大的功能,有了正则表达式,很多很复杂的问题就迎刃而解了。
比如我们从 price: 3.14$
中查找价格,没有正则表达式, 我们不得不遍历所有字符,判断是否是数字和小数点。有了正则表达式,我们只需要使用 \d+\.?\d*
就可以了。
sed 之所以那么早出名,也得益于它早早的就支持正则表达式了。
sed 的正则表达式非常强大,尤其是 GNU SED 这个版本,除了能够使用标准正则表达式,还支持 POSIX 类等正则表达式扩展。
今天,我们开始学习 sed 中的正则表达式。
标准正则表达式元字符
匹配行首 (^)
插入符号( ^
) 是标准正则表达式中的一个元字符,表示从一行的开头开始匹配。
例如 ^小明
只会匹配那些以 小明
开始的行。
范例
我们现在当前目录下新建一个文件 student.txt
,内容如下
小明,23岁,北京大学 小红,22岁,清华大学 小李,25岁,斯坦福大学 小王,22岁,清华大学
然后我们再测试 ^小明
是否只会匹配那些以 小明
开始的行。
[www.twle.cn]$ sed -n '/^小明/ p' student.txt
运行结果如下
小明,23岁,北京大学
匹配行尾 ($)
美元符号( $
) 是表示从一行必须以 $
符号前的文本结束。
例如 清华大学$
只会匹配那些以 清华大学
结束的行。
范例
我们现在当前目录下新建一个文件 student.txt
,内容如下
小明,23岁,北京大学 小红,22岁,清华大学 小李,25岁,斯坦福大学 小王,22岁,清华大学
然后我们再测试 清华大学$
只会匹配那些以 清华大学
结束的行。
[www.twle.cn]$ sed -n '/清华大学$/ p' student.txt
运行结果如下
小红,22岁,清华大学 小王,22岁,清华大学
匹配任意单个字符(.)
点号( .
) 是匹配任意单个非换行符的字符。也就是说是什么无所谓,但数量只能是 1 个。
例如 .at
匹配 bat
、cat
、mat
等等,但是不能匹配 at
或 ccat
等
[www.twle.cn]$ echo -e "cat\nbat\nrat\nmat\nbatting\nrats\nmats" | sed -n '/^.at$/p'
运行结果如下
cat bat rat mat
匹配任意存在字符列表中的一个字符( [] )
标准的正则表达式使用 方括号 []
表示字符列表,俗称字符集。
字符列表 []
用于匹配任意 单 个 存在 []
里的字符。
例如 [CT]
可以匹配单个 C
或 T
,但是不能匹配其它字符,也不能匹配 CT
。
[www.twle.cn]$ echo -e "Call\nTall\nBall" | sed -n '/[CT]all/ p'
运行结果如下
Call Tall
匹配任意不在字符列表中的一个字符 ( [^] )
标准的正则表达式使用 方括号 []
表示字符列表,俗称字符集。
字符列表 [^]
用于匹配任意 单 个 不存在 [^]
里的字符。
^
本身除外,如果不需要匹配^
则需要重复输入^
。
例如 [^CT]
可以任意单个字符,除了 C
或 T
。
[www.twle.cn]$ echo -e "Call\nTall\nBall" | sed -n '/[^CT]all/ p'
运行结果如下
Ball
匹配连续范围的字符 ( [-] )
所谓 连续范围的字符,就是按照 ASCII 表中出现的先后顺序排列的字符列表。
标准的正则表达式使用 破号 也就是 中划线( -
) 来表示连续范围的字符。
-
左边的字符表示开始,-
左边的字符表示结束。
例如 0-9
表示 0123456789
,例如 a-e
表示 abcde
。
标准的正则表达式使用 方括号 []
表示字符列表,俗称字符集。
因此 [-]
用于匹配任意 单 个 存在 连续范围的字符集 []
里的字符。
例如 [C-Z]
表示任意单个存在字符列表 [CDEFGHIJKLMNOPQRSTUVWXYZ]
里的字符。
[www.twle.cn]$ echo -e "Call\nTall\nBall" | sed -n '/[C-Z]all/ p'
运行结果如下
Call Tall
如果我们将连续字符集改为 A-P
[jerry]$ echo -e "Call\nTall\nBall" | sed -n '/[A-P]all/ p'
运行结果如下
Call Ball
只出现 0 次或 1 次 ( \?
)
标准的正则表达式使用 问号(?
) 表示前面的字符只出现 0 次或 1 次。
但是,因为 sed 将 ?
当作了普通字符,所以使用 \?
来表示前面的字符只出现 0 次或 1 次。
苹果电脑自带的 sed 不支持
\?
。
例如 Pea\?r
可以匹配 Per
或 Pear
,但不能匹配 Peaar
。
[www.twle.cn]$ echo -e "Pear\nPeaar\nPer" | sed -n '/Pea\?r/ p'
运行结果如下
Pear Per
至少出现一次 ( \+
)
标准的正则表达式使用 加号(+
) 表示前面的字符需要出现至少一次。
因为 sed 将 +
当作了普通字符,所以使用 \+
来表示前面的字符需要出现至少一次。
苹果电脑自带的 sed 不支持
\+
。
例如 Pea\+r
可以匹配 Pear
或 Peaar
,但不能匹配 Per
。
[www.twle.cn]$ echo -e "Pear\nPeaar\nPer" | sed -n '/Pea\+r/ p'
运行结果如下
Pear Peaar
出现任意次数 (*
)
标准的正则表达式使用 星号(*
) 表示前面的字符可以出现任意次数。也就是可以出现 0 次或 1 次或更多次数。
例如 ca*t
可以匹配 ct
、cat
或 caat
等等。
[www.twle.cn]$ echo -e "ct\nca\ncat\ncaat" | sed -n '/ca*t/ p'
运行结果如下
ct cat caat
精确出现 n 次 ( {n}
)
标准的正则表达式使用 {n}
表示前面的字符需要精确出现 n 次。
因为 sed 将 {n}
当作了普通字符,所以使用 \{n\}
来表示前面的字符需要精确出现 n 次。
例如 ^8\{3\}$
只能匹配 888
而不能匹配 88
或 8888
。
[www.twle.cn]$ echo -e "8\n88\n888\n8888" | sed -n '/^8\{3\}$/ p'
运行结果如下
888
至少出现 n 次 ( {n,}
)
标准的正则表达式使用 {n,}
表示前面的字符需要至少出现 n 次。
因为 sed 将 {}
当作了普通字符,所以使用 \{n,\}
来表示前面的字符需要至少出现 n 次。
例如 ^8\{3,\}$
可以匹配 888
或 8888
但不能匹配 88
。
[www.twle.cn]$ echo -e "8\n88\n888\n8888" | sed -n '/^8\{3,\}$/ p'
运行结果如下
888 8888
出现 M 到 N 次
{m, n}
用于表示前面字符的至少出现 m
次,最多出现 n
次。
因为 sed 将 {}
当作了普通字符,所以使用 \{m,n\}
来表示前面字符的至少出现 m
次,最多出现 n
次。
例如 ^8{2,4}$
可以匹配 88
、888
、8888
但是不会匹配 8
和 88888
。
[www.twle.cn]$ echo -e "8\n88\n888\n8888\n88888" | sed -n '/^8\{3,\}$/ p'
运行结果如下
888 8888 88888
管道符 (|)
竖线|
又称 管道符,在所有的正则表达式中都有着特殊的意义。它用于表示 或 的意思。
管道符通常和 小括号 ()
一起使用,用于从两个选择中匹配一个即可。
苹果电脑自带的 sed 不支持管道符 (|)。
例如正则表达式 /str\(1\|3\)/
既可以匹配 str1
也可以匹配 str3
。
[www.twle.cn]$ echo -e "str1\nstr2\nstr3\nstr4" | sed -n '/str\(1\|3\)/ p'
运行结果如下
str1 str3
注意: 管道符和小括号需要使用
\
转义。
正则表达式转义字符
正则表达式中还有一类特殊意义的字符,它们通常由 正斜杠(\
) 和小写字母组合而成。
例如,新行使用 \n
表示,回车使用 \r
表示等等等等。我们称这些字符为 转义字符。
sed 的正则表达式匹配这些特殊意义的转义字符需要再转义一次,也就是在前面在添加一个 正斜杠(\
)。
例如 \n
改成 \\n
,\r
改成 \\r
。
本小节,我们开始学习 sed 中支持的那些转义字符。
正斜杠 "\"
因为 正斜杠 "\" 和其它任意字母组合在一起都有一个特殊意思,就是转义这个字母。
因此如果要匹配 正斜杠 "\" ,则需要在前面额外添加一个 正斜杠 "\" 。
[www.twle.cn]$ echo 'str1\str2' | sed -n '/\\/ p'
运行结果如下
str1\str2
换行符 "\n"
\n
用于表示 换行符。
如果要匹配两个字母 \n
,那么在使用的时候需要额外添加一个 正斜杠(\
)。
[www.twle.cn]$ echo 'str1\nstr2' | sed -n '/\\n/ p'
运行结果如下
str1\nstr2
回车符 "\r"
\r
用于表示 回车符。
如果要匹配两个字母 \r
,那么在使用的时候需要额外添加一个 正斜杠(\
)。
[www.twle.cn]$ echo 'str1\rstr2' | sed -n '/\\r/ p'
运行结果如下
str1\rstr2
十进制字符 "\dnnn"
\dnnn
用于表示十进制字符 nnn
。
苹果电脑自带的 sed 不支持
\dnnn
。
例如 \d97
用于表示字母 a
,查 ASCII 表可得数字 97 表示字母 a
。
[www.twle.cn]$ echo -e "a\nb\nc" | sed -n '/\d97/ p'
运行结果如下
a
八进制 "\onnn"
\onnn
用于表示八进制字符 nnn
。
o
是字母 opq
中的 o
而不是数字 0
。
苹果电脑自带的 sed 不支持
\onnn
。
例如 \o142
用于表示字符 b
。 \o142
转换为十进制就是 98
,查 ASCII 表可得字母 b
。
[www.twle.cn]$ echo -e "a\nb\nc" | sed -n '/\o142/ p'
运行结果如下
b
十六进制 "\xnnn"
\xnnn
用于表示十六进制字符 nnn
。
苹果电脑自带的 sed 不支持
\xnnn
。
例如 \x64
用于表示字符 c
。 \o63
转换为十进制就是 99
,查 ASCII 表可得字母 c
。
[www.twle.cn]$ echo -e "a\nb\nc" | sed -n '/\x63/ p'
运行结果如下
c
POSIX 正则表达式
sed 支持 POSIX 规范下的正则表达式。
POSIX 正则表达式有个特点,就是支持一些具有特殊含义的 保留词。
这些特殊的 保留词 在 POSIX 中被称为 POSIX 类。
接下来的一小节,我们就来了解下 sed 支持的 POSIX 类。
字母数字 [:alnum:]
[:alnum:]
用于表示任意单个大小写字母 a-zA-Z
或单个数字 0-9
。
用上面所学的知识,[[:alnum:]]
其实就是 [0-9a-zA-Z]
。
例如 [[:alnum:]]
可以匹配 One
或 123
但是不能比配 \t
。
[www.twle.cn]$ echo -e "One\n123\n\t" | sed -n '/[[:alnum:]]/ p'
运行结果如下
One 123
字母 [:alpha:]
[:alpha:]
用于表示任意单个大小写字母 a-zA-Z
。
用上面所学的知识,[[:alpha:]]
其实就是 [a-zA-Z]
。
例如 [[:alpha:]]
可以匹配 One
但是不能匹配 123
或 \t
。
[www.twle.cn]$ echo -e "One\n123\n\t" | sed -n '/[[:alpha:]]/ p'
运行结果如下
One
空白符 [:blank:]
[:blank:]
用于表示任意单个空格 ' '
或制表符 \t
。
例如 [:blank:]
可以匹配 \t
但是不会匹配 One
或 123
。
[www.twle.cn]$ echo -e "One\n123\n\t" | sed -n '/[[:space:]]/ p' | cat -vte
运行结果如下
^I$
注意, 命令
cat -vte
会将制表符\t
显示为^I
数字 [:digit:]
[:digit:]
用于表示任意单个数字 0-9
。
用上面所学的知识,[[:digit:]]
其实就是 [0-9]
。
例如 [[:digit:]]
可以匹配 123
但是不能匹配 One
或 \t
。
[www.twle.cn]$ echo -e "abc\n123\n\t" | sed -n '/[[:digit:]]/ p'
运行结果如下
123
小写字母 [:lower:]
[:lower:]
用于表示任意单个小写字母 a-z
。
用上面所学的知识,[[:lower:]]
其实就是 [a-z]
。
例如 [[:lower:]]
可以匹配 one
但是不能匹配 TWO
或 \t
。
[www.twle.cn]$ echo -e "one\nTWO\n\t" | sed -n '/[[:lower:]]/ p'
运行结果如下
one
大写字母 [:upper:]
[:upper:]
用于表示任意单个大写字母 A-Z
。
用上面所学的知识,[[:upper:]]
其实就是 [A-Z]
。
例如 [[:upper:]]
可以匹配 TWO
但是不能匹配 one
或 \t
。
[www.twle.cn]$ echo -e "one\nTWO\n\t" | sed -n '/[[:upper:]]/ p'
运行结果如下
TWO
标点符号 [:punct:]
[:punct:]
用于表示任意单个标点符号。
例如 [:punct:]
可以匹配 One,123
但是不匹配 Three
和 123
[www.twle.cn]$ echo -e "One,Two\nThree\n123" | sed -n '/[[:punct:]]/ p'
运行结果如下
One,Two
空白符 [:space:]
[:space:]
用于表示任意单个空白符。
ASCII 编码中,空白符包含 空格(' ')、制表符('\t')、回车符('\r')、换行符('\n')、垂直制表符('\v') 和 换页符('\F')
例如 [:blank:]
可以匹配 \t
和 \f
但是不会匹配 One
。
[www.twle.cn]$ echo -e "One\n123\f\n456\t" | sed -n '/[[:space:]]/ p' | cat -vte
输出结果如下
123^L$ 456^I$
正则表达式元字符
跟其它语言的正则表达式一样,SED 同样支持正则表达式元字符。
元字符 通常由 正斜杠(\
) 和 一个字母组合而成。它们是一体的,放在一起表示单个字符而不是两个字符。 这也是元字符的由来。
sed 所支持的正则表达式元字符,是从 Perl 借鉴过来的。
需要注意,元字符几乎只有 GNU SED 支持,其它的 SED 变体可能就不支持了。比如苹果电脑自带的 sed 就不支持。
下面,我们开始简单的介绍下几个会经常用到的元字符。
单词边界 \b
在正则表达式中,\b
并不表示 回退,而是表示 单词边界。
苹果电脑自带的 sed 不支持
\b
。
那什么是单词边界呢?
单词边界就是一个单词和另一个单词的区分标志,通常是 空格 或 换行 等。
例如 /\bthe\b/
可以匹配 the
但是不能匹配 these
或 there
, they
, then
等等。
[www.twle.cn]$ echo -e "these\nthe\nthey\nthen" | sed -n '/\bthe\b/ p'
运行结果如下
the
非单词边界 \B
\B
和 \b
的作用正好相反,用于匹配非单词边界。
那什么是单词边界呢?
单词边界就是一个单词和另一个单词的区分标志,通常是 空格 或 换行 等。
例如 /the\B/
可以匹配 these
和 they
但是不能匹配 the
。
[www.twle.cn]$ echo -e "these\nthe\nthey" | sed -n '/the\B/ p'
运行结果如下
these they
单个空白符 \s
\s
用于匹配单个空白符。
ASCII 编码中,空白字符包含 空格(' ')、制表符('\t')、回车符('\r')、换行符('\n')、垂直制表符('\v') 和 换页符('\F')
苹果电脑自带的 sed 不支持
\s
。
例如 /Line\s/
可以匹配 Line\t
,但是不会匹配 Line2
。
[www.twle.cn]$ echo -e "Line\t\nLine2" | sed -n '/Line\s/ p'
运行结果如下
Line
单个非空格 \S
\S
用于匹配单个非空白符。
ASCII 编码中,空白字符包含 空格(' ')、制表符('\t')、回车符('\r')、换行符('\n')、垂直制表符('\v') 和 换页符('\F')
苹果电脑自带的 sed 不支持
\S
。
例如 /Line\S/
可以匹配 Line2
但是不会匹配 Line\t
。
[www.twle.cn]$ echo -e "Line\t1\nLine2" | sed -n '/Line\S/ p'
运行结果如下
Line2
单个字符 \w
\w
用于匹配单个字符。
并不是所有的字符都可以被匹配的,ASCII 编码中只有 数字 0-9、大小写字母 和 下划线 三种类型的字符才能被 \w
匹配到。
苹果电脑自带的 sed 不支持
\w
。
例如下面的命令
[www.twle.cn]$ echo -e "一\nOne\n123\n1_2\n&;#" | sed -n '/\w/ p'
运行结果如下
一 One 123 1_2
单个非字符 \W
从上面的学习中,我们知道 \w
用于匹配单个字符:数字 0-9、大小写字母 和 下划线。
而 \W
则是匹配 \w
之外的单个字符,也就是不包含在 数字 0-9、大小写字母 和 下划线 中的其它 ASCII 单个字符。
苹果电脑自带的 sed 不支持
\W
。
例如下面的命令
[www.twle.cn]$ echo -e "一\nOne\n123\n1_2\n&;#" | sed -n '/\W/ p'
运行结果如下
&;#
模式空间开始 ( '`' )
sed 提供了特殊字符 反引号(`)用于表示 模式空间 的开始。
` 是英文输入法状态下数字键 1 左边的那个键。
苹果电脑自带的 sed 不支持该特殊字符。
例如下面的 sed 命令
[www.twle.cn]$ echo -e "一二\n三\n一" | sed -n '/\`一/ p'
运行结果如下
一二 一