遵循 POSIX 标准的正则表达式

POSIX stands for Portable Operating System Interface (with the X added to the end for extra snappiness).

正则表达式的元字符(metacharacters):

^ $ . [ ] { } - ? * + ( ) | \

其它字符都被当成字面量处理。

  • Backslash character \ is used in a few cases to create metasequences, as well as allowing the metacharacters to be escaped and treated as literals instead of being interpreted as metacharacters.

大多数正则表达式的元字符在 shell 中展开(expansion)时有特殊含义,在将包含元字符的正则表达式传给命令行时,要用引号包裹,避免 shell 试图将元字符展开。

. 用于匹配任意字符,包括元字符、换行、. 等。

脱字符(插入符)^ 和美元符号 $ 是锚点,它们规定符合匹配规则的字符串出现在首或行尾才算匹配成功。
^$(a beginning and an end with nothing in between)匹配空行(blank lines)。

# What’s a five-letter word whose third letter is j and last letter is r that means xxx?
grep -i '^..j.r$' /usr/share/dict/words

[] 用于匹配括号内字符集中的一个字符

  • 集合中的元字符不再有特殊含义,只作为字面量,除了 ^-
    • 集合中的第一个字符若是 ^,则表示取反,表示匹配的是不包含在集合中的元素;集合中其它位置的 ^ 只作为字面量处理。
    • 若集合中第一个字符时 -,此时作字面量理解;集合中的其它位置出现的 - 表示字符范围

传统的字符范围:

grep -h '^[A-Za-z0-9]' dirlist*.txt
grep -h '[-AZ]' dirlist*.txt

不同 Linux 发行版执行以下命令的结果会不同(结果可能是空列表):

ls /usr/sbin/[ABCDEFGHIJKLMNOPQRSTUVWXYZ]*
ls /usr/sbin/[A-Z]*

# POSIX 字符集用于 shell 展开中
ls /usr/sbin/[[:upper:]]*

开发 Unix 的时期,只能识别 ASCII。ASCII 的 0-31 个字符是控制符;32-63 是可打印的字符,包括数字 0-9 和大多数的标点符号;64-95 包含大写字母和一些符号;96-127 包含小写字母和符号。所以使用 ASCII 的系统使用的是 collation order

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

正常的字典序:

aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ

随着 Unix 的流行,ASCII 表扩展到 8 比特,增加了 128-255 范围的字符,用于适配其它语言。

为了支持 ASCII 的扩容,POSIX 引入了 locale 的概念,用于选择某个地区的字符集。

$LANG 设置成 “en_US.UTF-8” 时,兼容 POSIX 的程序使用的是字典序,而不是 ASCII 中的顺序。

POSIX 包含一系列常用字符集:

字符集 描述
[:alpha:] 等价于 ASCII 的 [A-Za-z]
[:digit:] 数字 0 到 9
[:alnum:] 等价于 ASCII 的 [A-Za-z0-9]
[:word:] [:alnum:] 多一个 _
[:blank:] 包括 space, tab
[:space:] 包括 space, tab, carriage return, newline, vertical tab, form feed,等价于 ASCII 的 [ \t\r\n\v\f]
[:cntrl:] ASCII 控制符,包括 ASCII 字符 0 到 31,127
[:punct:] punctuation characters,等价于 ASCII 的 [-!"#$%&’()*+,./:;<=>?@[\]_`{
[:graph:] 可见字符,包括 ASCII 字符 33 到 126
[:print:] 可打印字符,比 [:graph:] 多了一个空格(space character)
[:lower:] 小写字母
[:upper:] 大写字母
[:xdigit:] 十六进制数字,等价于 ASCII 的 [0-9A-Fa-f]
# 查看系统的语言设置
echo $LANG
# en_US.UTF-8

# 查看 locale 设置
locale
# LANG="en_US.UTF-8"
# LC_COLLATE="en_US.UTF-8"
# LC_CTYPE="en_US.UTF-8"
# LC_MESSAGES="en_US.UTF-8"
# LC_MONETARY="en_US.UTF-8"
# LC_NUMERIC="en_US.UTF-8"
# LC_TIME="en_US.UTF-8"
# LC_ALL=

# converts the system to use US English (more specifically, ASCII, the traditional Unix behaviors, collation order) for its character set
export LANG=POSIX

POSIX 字符集可以用于正则表达式和 shell 展开。

POSIX 将正则表达式的实现分类两类:

  1. BRE (basic regular expressions)
    • 以上描述的特性,兼容 POSIX 且实现了 BRE 的程序均支持,如 grep
  2. ERE (extended regular expressions)

BRE 和 ERE 区别在于元字符

  • BRE 中只有 ^ $ . [ ] * 被当作元字符,其它都作为字面量;ERE 中 ( ) { } ? + |(and their associated functions)被当作元字符。
  • BRE 中,( ) { } 在用 \ 跳脱时被当成元字符;ERE 中元字符若用 \ 跳脱,则被当成字面量。

egrepgrep -E 可以使用 ERE 的特性。

Alternation |:

# 正则表达式被引号包起来,防止 shell 将 | 解析成管道操作符
echo "AAA" | grep -E 'AAA|BBB|CCC'
# AAA

# match the filenames that start with either bz, gz, or zip
grep -Eh '^(bz|gz|zip)' dirlist*.txt

# match any filename that begins with bz or contains gz or contains zip
grep -Eh '^bz|gz|zip' dirlist*.txt

? 表示匹配 0 次或 1 次,意味着 ? 前面的元素可选。

* 表示匹配 0 次或多次。

+ 匹配 1 次或多次。

{} 匹配指定的次数:

符号 含义
{n} Match the preceding element if it occurs exactly n times.
{n,m} Match the preceding element if it occurs at least n times but no more than m times.
{n,} Match the preceding element if it occurs n or more times.
{,m} Match the preceding element if it occurs no more than m times.

示例:

for i in {1..10}; do echo "(${RANDOM:0:3}) ${RANDOM:0:3}-${RANDOM:0:4}" >> phonelist.txt; done
# 校验电话号码
grep -Ev '^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$' phonelist.txt

# reveal pathnames that contain embedded spaces and other potentially offensive characters
find . -regex '.*[^-_./0-9a-zA-Z].*'

locate --regexp 支持 BRE,locate --regex 支持 ERE。

# perform a search for pathnames that contain either bin/bz, bin/gz, or /bin/zip
locate --regex 'bin/(bz|gz|zip)'

lessvim 中,按 / 后输入正则表达式执行搜索。
less 支持 ERE,会高亮匹配的字符串;vim 支持 BRE

less phonelist.txt
# /^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$

vim phonelist.txt
# /^([0-9]\{3\}) [0-9]\{3\}-[0-9]\{4\}$
# activate search highlighting
# :hlsearch
# 有些发行版的 vim 不完整,不支持搜索高亮,可以去安装完整版的 vim

less, vim:

  • 下一个匹配:n
  • 上一个匹配:N

The zgrep program provides a front end for grep, allowing it to read compressed files.

# search the compressed section 1 man page files
cd /usr/share/man/man1
# 包含字符串 regex 或字符串 regular expression 的文件,即用到正则表达式的程序
zgrep -El 'regex|regular expression' *.gz
  • back references: see sed

grep

grep (global regular expression print) 用于查找文本文件中符合正则表达式规则的字符并将包含字符的输出。

grep [options] regex [file...]

Option Long option Description
-i --ignore-case 不区分大小写
-v --invert-match 打印不包含匹配到的文本的行
-c --count 打印匹配成功的行数(和 -v 一起使用时是匹配不成功的行数)
-l --files-with-matches 打印包含满足匹配条件的文本的文件的名称
-L --files-without-match 打印不包含满足匹配条件的文本的文件的名称
-n --line-number 在输出行前加上行号的前缀
-h --no-filename 在多文件搜索中,不输出文件名
-A num --after-context=num 同时打印紧跟匹配成功的行后面的 num
-B num --before-context 同时打印匹配成功的行前面的 num
-C num --context=num 同时打印匹配成功的行前后的 num

JS

looking ahead (?=): matched but not consumed (not returned)

实际上 look ahead matches 有返回值,但值的长度是 0(0 character in length),因此 ?= 也称为 zero-width。

?= 匹配的是位置

邮箱匹配:

  • /(\w+\.)*\w+@(\w+\.)+[A-Za-z]+/.test('ben..forta@forta.com') 是 true 是因为 (\w+\.)* 不是贪心匹配,/(\w+\.)*\w+@(\w+\.)+[A-Za-z]+/.test('...forta@forta.com') 也是 true。
  • 加上起始锚点可以正确匹配:/^(\w+\.)*\w+@(\w+\.)+[A-Za-z]+$/.test('ben..forta@forta.com')

References