第十九章 正则表达式

前言

  1. 在下面几章,我们已经知道,文本数据在类 UNIX 系统中扮演这非常重要的角色。
  2. 但是,在领略这些工具强大的功能前,我们还是先看一下经常与这些工具的复杂用法相关联的技术-正则表达式。
  3. 前面我们已经接触命令行提供的许多特性和工具,并且也遇到过一些相当神秘的 shell 特性及命令。比如 shell 扩展和引用、键盘快捷键和命令历史记录等,更不用提 vi 编辑器了。
  4. 正则表达式也延续了这种传统,而且可以说是众多特性中最神秘的一个。

什么是正则表达式

  1. 简单地说,正则表达式是一种符号表示法,用于识别文本模式。在某种程度是上,它们类似于匹配文件和路径名时使用的 shell 通配符,但其用途更广泛。
  2. 许多命令行工具和大多数编程语言都支持正则表达式,以此来解决文本操作方面的问题。
  3. 然而,在不同的工具,以及不同的编程语言之间,正则表达式都会略有不同,这让事情进一步麻烦起来。
  4. 方便起见,我们将正则表达式的讨论限定在 POSIX 标准中,与许多编程语言不同,这些编程语言使用的符号集要更多一些。

grep - 文本搜索

  1. grep 基本介绍
  • 我们用来处理正则表达式的主要程序是 grep 。
  • grep 名字源于 global regular expression print ,由此可以看到,grep 与正则表达式有关。
  • 实际上,grep 搜索文本文件中于指定正则表达式匹配的行,并将结果送至标准输出。
  • 目前为止,我们已经利用 grep 搜索了固定的字符串,如下所示:
    1
    [me@liunxbox ~]$ ls /usr/bin | grep zip
  • 该命令行的作用是列出 /usr/bin 目录下文件名包含 zip 字符串的所有文件。
  • grep 程序按照如下方式接受选项和参数。
    1
    grep [options] regex [file...]
  • 其中字符串 regex 代表的是整个正则表达式。
  1. grep 选项
选项 功能描述
-i 忽略大小写。不区分大写和小写字符,也可以用 --ignore-case 指定
-v 不匹配。正常情况下, grep 会输出匹配行,而该选项可使 grep 输出不包含匹配项的所有行。也可以用 --invert-match 指定
-c 输出匹配项文件名而不是直接输出匹配行自身。也可以用 --files-without-matches 指定
-n 在每个匹配行前面加上该行在文件内的行号。也可以用 --line-number 指定
-h 进行多文件搜索时,抑制文件名输出。也可以用 --no-filename 指定
  1. grep 案例
  • 为了全面地了解 grep ,我们创建几个文本文件来进行搜索。
    1
    2
    3
    4
    5
    6
    [me@liunxbox ~]$ ls /bin > dirlist-bin.txt
    [me@liunxbox ~]$ ls /usr/bin > dirlist-usr-bin.txt
    [me@liunxbox ~]$ ls /sbin > dirlist-sbin.txt
    [me@liunxbox ~]$ ls /usr/sbin > dirlist-usr-sbin.txt
    [me@liunxbox ~]$ ls dirlist*.txt
    dirlist-bin.txt dirlist-sbin.txt dirlist-usr-sbin.txt dirlist-usr-bin.txt
  • 我们可以对文件列表执行简单搜索,如下所示:
    1
    2
    3
    [me@liunxbox ~]$ grep bzip dirlist*.txt
    dirlist-bin.txt:bzip2
    dirlist-bin.txt:bzip2recover
  • 本例中,grep 命令会搜索所有的文件,以查找字符串 bzip ,并找到了两个匹配项,而且这两个匹配项都在文件 dirlist-bin.txt 里。
  • 如果我们只对包含匹配项的文件感兴趣而不是对匹配项本身感兴趣,可以指定 -l 选项。
    1
    2
    [me@linuxbox ~]$ grep -l bzip dirlist*.txt
    dirlist-bin.txt
  • 相反,如果那只想查看那些不包含匹配项的文件,则可以用如下命令行。
    1
    2
    3
    4
    [me@linuxbox ~]$ grep -l bzip dirlist*.txt
    dirlist-sbin.txt
    dirlist-usr-bin.txt
    dirlist-usr-sbin.txt

元字符和文字

  1. 虽然看起来不是很明显,但 grep 搜索一直都在使用正则表达式,尽管那些例子都很简单。
  2. 正则表达式 bzip 用于匹配文本中至少包含 4 个字符、存在连续的按 b、z、i、p 顺序组成的字符串的行。
  3. 字符串 bzip 中的字符都是文字字符,即它们只能与自身进行匹配。除了文字字符,正则表达式还可以包含用于指定更为复杂的匹配的元字符。正则表达式的元字符包括以下字符。
    1
    ^ $ . [ ] {} - ? * + () | \
  4. 其他所有字符则被当做文字字符,但是在极少数的情况下,反斜杠字符用来创建元序列,以及用来对元字符进行转义,使其称为文字字符,而在被解释为元字符。
  5. 可以看到,当 shell 在执行扩展时,许多正则表达式的元字符在 shell 中具有特殊的含义。
  6. 所以,在命令行中输入包含元字符的正则表达式时,应把这些元字符用引号括起来以避免不必要的 shell 扩展。

任意字符

  1. 接下来讨论的第一个元字符是点字符或者句点字符,该字符用于匹配任意字符。如果将其加进某个正则表达式中,它将会在对应位置匹配任意字符。
  2. 下面就是一个应用实例。
    1
    [me@linuxbox ~]$ grep -h '.zip' dirlist*.txt
  3. 上述命令行,搜索到了所有匹配正则表达式 .zip 的命令行,但其输出结果有一些有趣的地方,比如说输出中并没有包含 zip 程序,这是因为正则表达会中的 . 元字符将匹配长度增加到了 4 个字符。
  4. 而 zip 只包含了三个字符,所以不匹配。同样,如果列表中某个文件包含了文件扩展名 .zip ,那么该文件也会被认为是匹配文件,因为文件扩展名中的 . 符号也被当做任意字符处理了。

  1. 插入符 ^ 和美元符号 $ 在正则表达式只与行的开头 ^ 或是末尾 $ 的内容进行匹配比较。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    [me@linuxbox ~]$ grep -h '^zip' dirlist*.txt
    zip
    zipcloak
    zipgrep
    zipinfo
    zipnote
    zipsplit
    [me@linuxbox ~]$ grep -h 'zip$' dirlist*.txt
    gunzip
    gzip
    funzip
    gpg-zip
    funzip
    gpg-zip
    preunzip
    prezip
    unzip
    zip
    [me@linuxbox ~]$ grep -h '^zip$' dirlist*.txt
    zip
  2. 上例中搜索的是行开头、行末尾都有字符串 zip (例如 zip 自动成一行) 的文件。请注意,正则表达式 ^$ (行开头和末尾之间没有字符) 将会匹配空行。

中括号表达式和字符类

基本介绍

  1. 中括号除了可以用于匹配正则表达式中给定位置的任意字符外,还可以用匹配指定字符集中单个字符。借助于中括号,我们可以指定要匹配的字符集 (也包括那些可能会被解释为元字符的字符) 。
  2. 如下命令行则利用了一个两个字母组成的字符集,用于匹配包含 bzip 或 gzip 字符串的文本行。
    1
    2
    3
    4
    [me@linuxbox ~]$ grep -h '[bg]zip' dirlist*.txt
    bzip2
    bzip2recover
    gzip
  3. 一个字符集可以包含任意数目的字符,并且当元字符放置到中括号中时,会失去它们的特殊含义。然而,在两种情况下,则会在中括号中使用元字符;并且当元字符放置到中括号中时,会失去它们的特殊含义。
  4. 然而,在两种情况下,则会在中括号中使用元字符,并且会有不同的含义。
  5. 第一个就是插入符 ^ ,它在中括号内使用表示否定;另外一个是连字符 - ,表示字符范围。

否定

  1. 如果中括号内的第一个字符是插入符 ^ ,那么剩下的字符则被当作不应该在指定位置出现的字符集。
  2. 作为演示,我们对前面的例子稍作修改。
    1
    [me@linuxbox ~]$ grep -h '[^bg]zip' dirlist*.txt
  3. 通过使用否定操作,我们可以得到哪些包含 zip 字符但 zip 前面既不是 b 也不是 g 的所有程序。请注意,此时 zip 命令仍然没有出现在结果列表中,由此可见否定,字符集仍然需要在指定位置有对应字符,只不过这个字符不是否定字符集中的成员而已。
  4. 插入符号 ^ 只有是中括号表达式中的第一个字符时才会被当作否定符,如果不是第一个,^ 将会丧失其特殊含义而成为普通字符。

传统字符范围

  1. 如果我们希望建立一个正则表达式,用于查找文件名以答谢字母开头的文件,可以用下面的命令行。
    1
    [me@linuxbox ~]$ grep -h '^[ABCDEFGHIJKLMNOPQRSTUVWXZY]' dirlist*.txt
  2. 这仅仅是将 26 个大写字母写入中括号的小事,但是要输入 26 个字母是在有点麻烦,我们可以用下面的简单方法完成。
    1
    2
    3
    4
    5
    6
    7
    8
    [me@linuxbox ~]$ grep -h '^[A-Z]' dirlist*.txt
    MAKEDEV
    ControlPanel
    GET
    HEAD
    POST
    X
    X11
  3. 通过使用三个字符表示的字符范围,我们可以缩写这 26 个字母。能够按照这种方式表达的任何字符范围可以包含多个范围,比如下面这个表达式可以匹配以字母和数字开头的所有文件名。
    1
    [me@linuxbox ~]$ grep -h '^[A-Za-z0-9]' dirlist*.txt
  4. 在字符范围中,可以看到连字符有了特殊的用法,那么如何在中括号中真正包括一个连字符字符,可将连字符作为中括号内的第一个字符,示例如下:
    1
    [me@linuxbox ~]$ grep -h '^[-AZ]' dirlist*.txt
  5. 匹配的则是文件名包含连字符、大写字母 A 或大写字母 Z 的文件。

POSIX 字符类

  1. 基本介绍
  • 传统的字符范围表示方法很容易理解,而且能够有效、快速地指定字符集。
  • 但不足之处在于,他并不是所有情况都是用。虽然到目前为止,在使用 grep 命令时还没有遇到过任何问题,但是在其他程序中则可能会遇到问题。
  • 在第 4 章,我们讨论了如何使用通配符来执行路径名扩展。在该讨论中我们提到,字符范围的用法几乎与其正则表达式中一致,现在问题出现了。
    1
    2
    3
    [me@linuxbox ~]$ ls /usr/sbin/[ABCDEFGHIJKLMNOPQRSTUVWXYZ]*
    /usr/sbin/MAKEFLOPPIES
    /usr/sbin/NetworkManagerDispatcher
  • Linux 发行版本不同,上述命令行得到的结果可能会不同,甚至有可能是空列表。本例中的列表来自于 Ubuntu 系统。该命令行得到预期效果-只有一大些字母开头的文件列表。但是,如果我们使用下面的命令行,便会得到完全不同的结果 (只显示了输出结果的一部分) 。
    1
    2
    3
    4
    5
    [me@linuxbox ~]$ ls /usr/sbin/[A-Z]*
    /usr.sbin/biosdecode
    /usr/sbin/chat
    /usr/sbin/chpasswd
    /usr/sbin/chroot
  • 在 UNIX 开发初期,它只识别 ASCII 字符,而正是这一特性导致了上面的差异。在 ASCII 码中,前 32 个字符 (第 0-32 字符) 都是控制字符 (像 Tab 键、空格键以及 Enter 键等) ,后 32 个字符 (第 32-63) 包含可打印字符,包括大多数的标点符号以及数字 0-9 ,接下来的 32 个 (第 64-95) 包含大写字母和一些标点符号,最后的 31 个 (第 96-127) 则包含小写字母以及更多的标点符号。基于这样的安排,使用 ASCII 的系统使用了下面这种排序:
    1
    ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
  • 这与通常的字典顺序不一样,字典中的字母的顺序表通常如下:
    1
    aAbBcCdDeEfFGhHiIjJKKlLmMnNoOpPqQrRsStuUvVwWxXyYzZ
  • 随着 UNIX 在美国以外国家的普及,人们越来越希望计算机能支持没事英语中找不到的字符。于是,ASCII 字符表也得以扩展,开始使用 8 位二进制来表示,这也就增加了第 188~255 的字符,兼容了更多的语言。
  • 为了支持这种功能,POSIX 标准引入域 (locale) 的概念,它通过不停调整以选择特定的位置所需要的字符集。我们可以使用下面的命令行查看系统的语言设置。
    1
    2
    [me@linuxbox ~]$ echo $LANG
    en_US.UTF-8
  • 有了这个设置,POSIX 兼容的应用程序使用的便是字典中的字典排行顺序,而不是用 ASCII 码中的字符排序顺序。这样,便解释了上面命令行的诡异行为。
  • A-Z 的字符范围,用字典的顺序全是时,包括了字母表中出了小写字母 a 的所有字母,因此使用命令行 ls/usr/sbin/[A-Z]* 才会出现全然不同的结果。
  1. POSIX 字符类
字符串 描述
[:alnum:] 字母字符和数字字符;在 ASCII 码中,与 [A-Za-z0-9] 等效
[:word:] 基本与 [:alnum] 一样,只是多了一个下划线字符 (_)
[:alpha:] 字母字符:在 ASCII 中,等效于 [A-Za-z]
[:bank:] 包括空格和制表符
[:cntrl:] ASCII 控制码: 包括 ASCII 字符 0~31 以及 127
[:digit:] 数字 0~9
[:graph:] 可见字符;在 ASCII 中,包括字符 33~126
[:lower:] 小写字母
[:punct:] 标点符号字符;在 ASCII 中,与 [-!”#$%&’()*+,./:;<=>?@[\]_{
[:print:] 可打印字符;包括 [:graph:] 中的所有字符再加上空格字符
[:space:] 空白字符如空格符、制表符、回车符、换行符、垂直制表符以及换页符。在 ASCII` 中,等效为 [\t\r\n\v\f]
[:upper:] 大写字母
[:xdigit:] 用于表示十六进制的字符;在 ASCII 中,与 [0-9A-Fa-f] 等效
  1. POSIX 补充说明
  • 当然,即便是有了这么多字符类,仍然没有比较方便的方法表示部分范围,如 [A-M] 。
  • 使用字符类,我们可以重复上述大写字母的例子,并得到改善的输出结果。
    1
    2
    [me@linuxbox ~]$ ls /usr/sbin/[[:upper:]]*
    /usr/sbin/MAKEFLOPPIES
  • 然而,请记住,上述并不是一个正则表达式的示例,它其实是 shell 路径名扩展的一个例子。在此处提及,主要是因为这两种用法都支持 POSIX 字符类。

POSIX 基本正则表达式和扩展正则表达式的比较

  1. POSIX 规范将正则表达式的实现方式分为了两种:基本正则表达式 (BRE) 和扩展正则表达式 (ERE) 。
  2. 在 BRE 方式中,只承认 ^、$、. 、[、]、* 这些事元字符,所有其他的字符都被识别为文字字符。而在ERE中,添加了(、)、{、}、?、+|、等元字符 (及其相关功能) 。
  3. 只有在用反斜杠进行转义的情况下,字符 (、)、{、} 才会被当做元字符处理,而 ERE 中,任何元字符前面加入反斜杠反而会使其被当作文字字符来处理。
  4. 由于下面要讨论的特性是 ERE 的一部分,所以需要使用不一样的 grep 。传统上,这是由 egrep 程序来执行的,但是 GNU 版本的 grep 可以运用 -E 选项以支持 ERE 方式。

或选项

  1. 我们将要讨论的第一个扩展正则表达式的特性是或选项 (alternation) ,它是用于匹配表达式集的工具。括号表达式可以从指定字符集匹配单一字符,而或选项则用于从字符串或正则表达式集中寻求匹配项。
  2. 首先,我们进行一个简单的子字符串匹配
    1
    2
    3
    4
    [me@linuxbox ~]$ echo "AAA" | grep "AAA"
    AAA
    [me@linuxbox ~]$ echo "BBB" | grep "AAA"
    [me@linuxbox ~]$
  3. 这是一个直白的例子,将 echo 的输出结果送至 grep 进行匹配搜索。如果匹配成功,结果便输出打印出来,如无匹配项,则无结果输出。
  4. 现在添加或选项,它用元字符 | 表示。
    1
    2
    3
    4
    5
    6
    [me@linuxbox ~]$ echo "AAA" | grep -E "AAA|BBB"

    [me@linuxbox ~]$ echo "BBB" | grep -E "AAA|BBB"
    BBB
    [me@linuxbox ~]$ echo "CCC" | grep -E "AAA|BBB"
    [me@linuxbox ~]$
  5. 这里出现了 “AAA|BBB” 正则表达式,此表达式的含义是匹配字符串 AAA 或者匹配字符串 BBB 。请注意,由于此处使用的是扩展特性,所以 grep 增加了 -E 选项 (虽然可以使用 egrep 命令来代替) ,并且将正则表达式用引号引起来以防止 shell 将元字符 | 当做管道操作符来处理。
  6. 另外或选项并不局限于两种选择,还可以有更多的选择项。
    1
    2
    [me@linuxbox ~]$ echo "AAA" | grep -E "AAA|BBB|CCC"
    AAA
  7. 为了将或选项可与其他正则表达式符号结合使用符号结合使用,我们可以用 () 将或选项的所有元素与其他符号隔开。
    1
    [me@linuxbox ~]$ grep -Eh "^(bz|gz|zip)" dirlist*.txt
  8. 以上表达式的含义是匹配以 bz、gz 或是 zip 开头的文件。如果不使用括号 () ,该正则表达式的含义就完全不同,其匹配的便是文件名以 bz 开头或者是包含 gz 和 zip 文件。
    1
    [me@linuxbox ~]$ grep -Eh "^bz|gz|zip" dirlist*.txt

限定符

基本介绍

  • 扩展正则表达式 (ERE) 提供多种方法指定某元素匹配的次数。

? - 匹配某元素 0 次或 1 次

  1. 该限定符实际上意味着前面的元素可选。
  2. 所谓电话号码有效,指的是电话号码必须是下面两种形式 (nnn)nnn-nnnn 和 nnn nnn-nnnn 中的一种,其中 n 是数值。于是,我们构造如下所示的正则表达式。
    1
    ^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$
  3. 此表达式中,括号字符的后面增加了 ? 符号以表示括号字符只能匹配一次或零次。同样,由于括号字符在 ERE 中通常是元字符,所以其前面加上了反斜杠告诉 shell 此括号为文字字符,所以其前面加上了反斜杠告诉 shell 此括号为文字字符。
    1
    2
    3
    4
    5
    6
    [me@linuxbox ~]$ echo "(555) 123-4567" | grep -E "^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$"
    (555) 123-4567
    [me@linuxbox ~]$ echo "555 123-4567" | grep -E "^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$"
    555 123-4567
    [me@linuxbox ~]$ echo "AAA 123-4567" | grep -E "^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$"
    [me@linuxbox ~]$
  4. 由此可以看出,该表达式匹配了上述两种形式的电话号码,但不匹配那些非数字字符的号码。

* - 匹配某元素多次或零次

  1. 与 ?元字符类似,* 用于表示一个可选择的条目。然后,与 ? 不同,该条目可以多次出现,而不仅仅是一次。
  2. 例如,如果我们想知道一串字符是否是一句话,也就是说,这串字符是否以大写字母开头而以句号结束,并且中间内容是任意数目的大小写字母和空格,那么要匹配这种非常粗糙的句子定义,可以用如下正则表达式。
    1
    [[:upper:]][[:upper:][:lower:] ]*\.
  3. 该表达式匹配前两个测试语句,但是不匹配第三个,原因是它的首字母不是大写并且末尾没有句号。
    1
    2
    3
    4
    5
    6
    [me@linuxbox ~]$ echo "This works." | grep -E "[[:upper:]][[:upper:][:lower:] ]*\."
    This works.
    [me@linuxbox ~]$ echo "This Works." | grep -E "[[:upper:]][[:upper:][:lower:] ]*\."
    This Works.
    [me@linuxbox ~]$ echo "this does not" | grep -E "[[:upper:]][[:upper:][:lower:] ]*\."
    [me@linuxbox ~]$
  4. 该表达式匹配前面两个测试语句,但是不匹配第三个,原因是它的首字母不是大写并且末尾没有句号。

+ - 匹配某元素一次或多次

    • 元字符与 * 非常类似,只是 + 要求置于其前面的元素至少出现一次。
  1. 示例如下,该正则表达式用于匹配由单个空格分隔的一个或者多个字母字符组成的行。
    1
    ^([[:alpha:]+ ?])+$
  2. 示例如下
    1
    2
    3
    4
    5
    6
    7
    8
    [me@linuxbox ~]$ echo "This that" | grep -E "^([[:alpha:]]+ ?)+$"
    This that
    [me@linuxbox ~]$ echo "a b c" | grep -E "^([[:alpha:]]+ ?)+$"
    a b c
    [me@linuxbox ~]$ echo "a b 9" | grep -E "^([[:alpha:]]+ ?)+$"
    [me@linuxbox ~]$
    [me@linuxbox ~]$ echo "a b d" | grep -E "^([[:alpha:]]+ ?)+$"
    [me@linuxbox ~]$
  3. 我们可以看到此表达式并不匹配 a b 9 这一行,因为该行包含了非字母字符 9 ,同样也不匹配 abc d 这一行,因此c和d之间被多个空格符分开了

{} - 以指定次数匹配某元素

  1. {} 指定匹配次数
指定项 含义
{n} 前面的元素恰好出现 n 次则匹配
{n,m} 前面的元素出现的次数在 n~m 次之间时则匹配
{n,} 前面的元素出现次数超过 n 次则匹配
{,m} 前面的元素出现次数不超过 m 次则匹配
  1. {} 案例
  • 回到前面电话号码的例子,我们可以运用此处讲到的指定重复次数的方法将原来的正则表达式 “^(?[0-9][0-9][0-9])? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]” 简化为 “^(?[0-9]{3})? [0-9]{3}-[0-9]{4}”
  • 示例如下
    1
    2
    3
    4
    5
    6
    [me@linuxbox ~]$ echo "(555) 123-4567" | grep -E "^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}"
    (555) 123-4567
    [me@linuxbox ~]$ echo "555 123-4567" | grep -E "^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}"
    555 123-4567
    [me@linuxbox ~]$ echo "5555 123-4567" | grep -E "^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}"
    [me@linuxbox ~]$
  • 结果表明,修改后的表达式不管有无括号都可验证数字的有效性,并剔除那些格式不正确的数字。

正则表达式的应用

基本介绍

  1. 让我们回顾一些前面已经用过的命令,观察它们如何使用正则表达式。

用 grep 命令验证号码簿

  1. 前面的例子中,我们只验证了一个电话号码的有效性,而检验一个号码列表往往才是实际需求,所以我们先创建一个号码列表。
  2. 于是,我们在命令行中输入一些神奇的咒语以创建号码列表,之所以称其为神奇是因为里面多数命令到目前为止本书还未涉及到。
    1
    [me@linuxbox ~]$ for i in {1..10}; do echo "(${RANDOM:0:3}) ${RANDOM:0:3}-${RANDOM:0:4}" >> phonelist.txt; done
  3. 该命令行会产生一个包含 10 个电话号码的名为 phonelist.txt 的文件。每次重复该命令行,该列表就会添加 10 个号码。我们也可以通过更改命令行前端的数字 10 来指定创建更多或更少的电话号码。
  4. 然而,如果检查文件内容,我们就会发现如下问题。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [me@linuxbox ~]$ cat phonelist.txt
    (263) 302-9778
    (120) 109-1260
    (986) 320-2757
    (276) 946-6691
    (287) 127-1098
    (176) 148-5524
    (144) 134-2784
    (200) 175-1285
    (292) 108-518
    (129) 44-1379
  5. 其中有部分数字是畸形的,而这正好满足我们的练习要求,因为接下来就是要用 grep 判断它们的有效性。
  6. 进行有效验证,可对文件内容进行扫描以搜索无效数字,并将结果显示出来。
    1
    2
    3
    4
    [me@linuxbox ~]$ grep -Ev "^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$ phonelist.txt
    (292) 108-518
    (129) 44-1379
    [me@linuxbox ~]$
  7. 此处我们使用 -v 选项输出相反的结果,也就是输出列表中不符合指定表达式的那些行。此表达式本身在每行末尾使用锚元字符,从而确保每个数字末尾没有多余的字符。

用 find 查找奇怪的文件名的文件

  1. find 命令的 test 选项可以用正则表达式表示。运用正则表达式时,find 和 grep 有一点不同,grep 命令是搜索哪些只包含与指定表达式匹配的字符串的行,而 find 则要求文件名与指定表达式完全一致。
  2. 下面的例子中,我们将利用 find 命令结合正则表达式来查找文件名中不包含如下所示集合中的任一字符的文件。
    1
    [-_./0-9a-zA-Z]
  3. 用此表达式进行搜索,将会输出文件名中包含内嵌空格以及其他潜在不规范字符的文件。
    1
    [me@linuxbox ~]$ find . -regex ".*[^-_./0-9a-zA-Z].*"
  4. 由于要求整个路径名与正则表达式的描述完全一致,所以表达式的两端增加了 .* 以匹配 0 个或多个字符。在表达式的中间部分,我们使用了一个否定的中括号表达式,其中包含了可接受的路径名字的字符集。

用 locate 查找文件

  1. locate 命令既支持基本正则表达式 (--regexp 选项),也支持扩展正则表达式 (--regex 选项) 。利用 locate , 可以完成之前对 dirlist 所做的许多操作。
    1
    2
    3
    4
    5
    6
    7
    [me@linuxbox ~]$ locate --regex "bin/(bz|gz|zip)"
    /bin/bzcat
    /bin/bzcmp
    /bin/gzexe
    /bin/gzip
    /usr/bin/zip
    /usr/bin/zipcloak
  2. 利用或选项,我们搜索到了那些路径包含 bin/bz、bin/gz 或 bin/zip 等字符串的文件。

利用 less 和 vim 命令搜索文本

  1. less 和 vim 采用同样的方法进行文本搜索。按下 / 键后输入正则表达式,即开始进行搜索。
  2. 我们可以利用 less 查看 phonelist.txt 文件的内容:
    1
    [me@linuxbox ~]$ less phonelist.txt
  3. 按下 / 键后输入正则表达式,即开始进行搜索。less 会以高亮的方式显示匹配字符串,这样就很容易找到那些无效的号码。
  4. 另一方面,vim 也支持基本正则表达式,所以,我们可以用下面的搜索表达式。
    1
    /([0-9]\{3\}) [0-9]\{3\}-[0-9]\{4\}
  5. 可以看到,基本表达式基本一样。然而,在扩展表达式中被当作元字符的许多字符在基本表达中则被看作文字字符,只有使用反斜杠转义后才会被当作元字符。匹配项是否以高亮形式显示,则取决于自己系统上 vim 的设置。

参考文章

  • 转载:Linux 命令行大全