天天看点

学习 Linux,101: 使用正则表达式搜索文本文件

<a>概述</a>

本文深入介绍基础的 Linux 进程管理技术。您将学习如何:

创建简单的正则表达式

使用正则表达式搜索文件和文件系统

使用正则表达式和 sed

本文帮助您准备 Linux Professional Institute's Junior Level Administration (LPIC-1) 考试的 103 主题下的 103.7 考核目标。该考核目标的权值为 2。

<a>先决条件</a>

<a href="http://www.ibm.com/developerworks/cn/linux/l-lpic1-v3-103-7/#ibm-pcon">回页首</a>

<a>设置示例</a>

<a>清单 1. 创建示例文件</a>

您的窗口应类似于清单 2,当前目录现在是 lpi103-7 目录中新建的目录。

<a>清单 2. 创建示例文件——输出</a>

<a>正则表达式</a>

正则表达式在计算机语言理论中有很长的历史。大部分计算机学科的学生都知道,可以使用正则表达式表示的语言与有限时序机(finite automata)可以接受的语言一样。本文中的正则表达式所代表的含义更为复杂,与您在计算机科学课堂上学到的内容可能不同,虽然传承是一样的。

<code>grep</code> 和 <code>sed</code> 使用的正则表达式。使用正则表达式的另一个程序是 <code>awk</code>。

<a>基本的构建块</a>

大部分 Linux 系统中的 GNU 程序可以使用两种常规表达式语法:basic 和 extended。使用 GNU                grep,功能上没有不同之处。本文将介绍基本的语法,以及它和扩展语法之间的不同之处。

正则表达式通过元字符 加强的字符 和操作符 构建。大部分字符与自身匹配,大部分元字符必须使用反斜杠(\)进行转义。基本的操作包括:

<dl></dl>

<dt>连接</dt>

<dd>连接两个正则表达式创建一个更长的正则表达式。例如,正则表达式 a 匹配字符串 abcdcba 两次,正则表达式</dd>

b 也是一样。但是,ab 将只匹配 abcdcba,而

ba 将只匹配 abcdcba。

<dt>重复</dt>

<dd>Kleene * 或重复操作符将匹配 0 次或多次前一个正则表达式。因此像 a*b 之类的表达式将匹配以任何以</dd>

a 开头以 b 结尾的字符串,包括 b 本身。Kleene * 不用转义,因此希望匹配字面值星号(*)的表达式必须让星号转义。这里使用的 * 与通配符中使用的 * 不同,通配符中的 * 号匹配任何字符串。

<dt>交替</dt>

<dd>交替操作符(|)匹配前置或后置表达式。它必须匹配前一个或后一个表达式之一。在基本语法中它必须转义。例如,表达式 a*\|b*c 匹配由任何数量的</dd>

a 或 b 组成(但不是同时)且以一个 c 结尾的字符串。同样,单个字符

c 也是匹配的。

尽量不要引用正则表达式以避免 shell 膨胀。

<a>搜索文件和文件系统</a>

<code>grep</code> 使用一个正则表达式作为参数,还有 0 个或多个要搜索的文件。如果没有给定文件,grep 将搜索 stdin,这让它成为一个可以在管道中使用的过滤器。如果没有匹配任何行,则

<code>grep</code> 没有输出,尽管可以测试它的退出代码。

<a>清单 3. 简单的正则表达式</a>

从上例中可以看出,有时候会得到出乎意料的结果,尤其是使用重复的时候。您可能预期 p* 或者 pp* 能够匹配几个带

p 的字符串,但是 p* 和 x* 能匹配文件的所有行,因为 * 操作符匹配

0 次或多次前一个正则表达式。

有两个示例演示了从 grep 退出的代码。如果找到匹配,则返回值 0。如果发生错误,比如要搜索的文件不存在,则返回大于 1 的值(GNU grep 总是返回 2)。

<a>快捷键</a>

现在可以使用 <code>grep</code> 和基本的正则表达式构建块了,以下是一些方便的快捷键。

<dt>+</dt>

<dd>+ 操作符类似于 * 操作符,但是它匹配一次或多次前一个正则表达式。基本表达式中它必须转义。</dd>

<dt>?</dt>

<dd>? 表示前一个表达式是可选的,因此它表示匹配 0 次或 1 次。这与通配符中使用的 ? 不同。</dd>

<dt>.</dt>

<dd>.(句点)是表示任何字符的元字符。最常使用的方式是 .*,该表达式匹配包含任何字符(或没有字符)的任意长度的字符串。不用说您就明白,这一般在一个较长的表达式中使用。比较句点与通配符中使用的 ?,.* 与通配符中使用的 *。</dd>

<a>清单 4. 更多正则表达式</a>

<a>匹配一行的开始或结束</a>

^(脱字符号)匹配一行的开始,$(美元符号)匹配行的结束。^..b 匹配行开始处任何后跟 b 的两个字符,而

ar$ 匹配任何以 ar 结束的行。正则表达式 ^$ 匹配空行。

<a>更复杂的表达式</a>

到目前为止,我们已经学习了用于单个字符的重复。如果希望搜索一个或多个多字符字符串,比如 banan 中 an 出现了两次,那么可以使用圆括号,在基本语法中必须转义。类似地,您可能希望搜索一些字符,但又不想使用 . 这么通用或者交替这么啰嗦的表达式。那么,您可以使用方括号([])将交替情况括起来,常规语法需要转义。方括号中的表达式构成了一个字符类。使用方括号还可以减少转义特殊字符(比如 . 和 *)的需求,例外情况见后文。

<a>清单 5. 圆括号和字符类</a>

字符类还有几个有趣的可能性。

<dt>范围表达式(Range expression)</dt>

<dd>范围表达式是使用 -(连字符)分隔的双字符,比如数字里面的 0-9,十六进制里的 0-9a-fA-F。注意,范围与语言环境有关。</dd>

<dt>署名类(Named class)</dt>

<dd>有些署名类可以为通常使用的类提供便捷。署名类以 [: 开始,以 :] 结束,可以在括号表达式中使用。示例如下:</dd>

<dt>[:alnum:]</dt>

<dd>字母数字字符</dd>

<dt>[:blank:]</dt>

<dd>空格和制表符</dd>

<dt>[:digit:]</dt>

<dd>数字 0 到 9(等效于 0-9)</dd>

<dt>[:upper:] 和 [:lower:]</dt>

<dd>分别为大写字母和小写字母。</dd>

<dt>^(求反)</dt>

<dd>在字符类 [ 后的第一个字符使用时,^(脱字符号)对剩余字符求反,因此只有类中不存在该字符时(前导^除外)才能匹配。               </dd>

了解了以上特殊含义后我们知道,如果希望匹配一个字符类中的字面值 -(连字符),那么您必须将其放在第一个或最后一个。如果想匹配字面值^(脱字字符),那么它不能是第一个字符。] 在非第一个位置时表示结束类。

字符类中,正则表达式和通配符是类似的,但使用的否定符号不同(^ 和 !)。清单 6 展示了一些字符类示例。

<a>清单 6. 更多字符类</a>

最后一个示例让您感到奇怪吗? 在这种情况下,第一个括号表达式匹配字符串中的任何数字、 n 或 z,至少 n 后面没有另一个 n 或 r,因此字符串结尾处的 na 匹配该正则表达式。

<a>哪些内容匹配?</a>

如果您能够区分高亮显示,比如用颜色、粗体或下划线,那么您可以设置 GREP_COLORS 环境变量来高亮显示匹配内容。默认设置使用粗体红色高亮显示匹配内容,如图 1 所示。您会看到整个输出的第一行都是匹配的,但是第二行只匹配最后两个字符。

<a>图 1. 使用颜色区分 grep 匹配内容</a>

如果您是正则表达式新手,或者不确定 grep 为什么返回某一行,那么这项技术可以帮您。

<a>扩展的正则表达式</a>

扩展的正则表达式语法是 GNU 扩展。我们在基本语法中使用时,它不需要转义一些字符,包括圆括号、'?'、'+'、'|'和 '{'。但缺点在于,如果您在正则表达式中将它们作为字符解释,那么必须进行转义。您可以使用

<code>-E</code>(或者 grep 的 <code>--extended-regexp</code> 选项)表示您正在使用扩展的正则表达式语法。此外,<code>egrep</code> 命令也可以帮助您实现这一点。清单 7 展示了本节上文中使用的示例,以及

<code>egrep</code> 使用的相应扩展表达式。

<a>清单 7. 扩展的正则表达式</a>

<a>在文件中查找内容</a>

现在您了解了基本的命令,让我们使用 <code>grep</code> 和 <code>find</code> 在文件系统中查找内容。示例相对比较简单;它们使用

首先,<code>grep</code> 可以一次搜索多个文件。如果添加 <code>-n</code> 选项,它将告诉您匹配的行号。如果只想知道匹配多少行,可以使用

<code>-c</code> 选项,如果只想获得匹配的文件列表,可以使用 <code>-l</code> 选项。清单 8 展示了一些示例。

<a>清单 8. 搜索多个文件</a>

查看清单 8 中的 <code>-c</code> 选项,您会看到一行 <code>text3:0</code>。                 您经常需要知道某个内容在文件中出现了多少次,但是不用知道没有出现该内容的文件。<code>grep</code> 命令有一个

<code>-v</code> 选项,它表示只显示不匹配的行输出。因此,我们可以使用正则表达式 <code>:0$</code> 查找以逗号和 0 结尾的行。

下一个示例是使用 <code>find</code> 查找当前目录及其子目录中的所有常规文件,然后使用 <code>xargs</code> 将文件列表传递到

<code>grep</code>,以确定每个文件中出现 banana 的次数。最后,通过再一次调用 <code>grep</code> 筛选该输出,这一次使用

<code>-v</code> 选项查找所有不以 :0 结尾的行,只用告诉我们包含字符串 banana 的文件计数。

<a>清单 9. 查找至少包含一次 banana 的文件</a>

<a>正则表达式和                sed</a>

如果您需要查找某内容,那么只需要使用 <code>grep</code>。如果需要从匹配行中提取搜索字符串,或者相关字符串,那么需要进一步操作,您可以选择使用

<code>sed</code>。让我们解释一下它的工作方式。首先回忆我们的两个示例文件,text1 和                text2,其中包含了一个数字,后跟空格,再加一个水果的名称,而 text3 包含重复的语句。我们在清单 10 中再看一次它的内容。

<a>清单 10. text1、text2 和 text3 的内容</a>

首先,我们将使用 <code>grep</code> 和 <code>sed</code> 提取以一个或多个数字开头,且后跟空白字符(空格或制表符)的行。一般情况下,<code>sed</code> 在一个周期结束时打印出每个行,因此我们使用 sed 的

<code>-n</code> 选项禁止输出,然后使用 <code>sed</code> 中的 <code>p</code> 命令只打印匹配我们正则表达式的行。要确认我们对这两个工具使用的正则表达式相同,我们将其赋予一个变量。

<a>清单 11. 搜索 grep 和 sed</a>

注意,<code>grep</code> 在搜索到多个文件时将显示文件名称。因为我们使用 <code>cat</code> 提供 <code>sed</code> 的输出,所以

<code>sed</code> 无法知道源文件名。但是,匹配行是相同的,正如我们期望的那样。

现在假设我们只需要找到的行中的第二个字。在本例中是水果的名称,但是我们需要查询 HTTP URL 或者文件名等等其他内容。例如,删除我们试图匹配的字符串就足够了,如清单 12 所示。

<a>清单 12. 使用 sed 删除前导数字</a>

对于最后一个示例,假设我们的行在水果名称之后还有些内容。我们添加了一行 “lemon pie”,查看如何只提取 lemon。我们将对输出排序,放弃非唯一的值,因此我们得到一个找到的水果列表,每个水果只出现一次。

清单 13 展示了两种实现同一个任务的方式。首先,我们剔除了前导数字以及后面的空格,然后剔除第一个空格或选项卡之后的所有内容,并打印剩下的内容。在第二个示例中,我们引入了圆括号将整个行分为 3 个部分,数字和后面的空格、第二个字以及其他内容。我们使用

<code>s</code> 命令将整个行替换为第二个字,然后打印结果。您可以尝试变化一下方式,忽略第三部分,\(.*\),看看是否能解释发生了什么。

<a>清单 13. 获取水果名</a>

有些旧版本的 <code>sed</code> 不支持扩展的正则表达式。如果您的 <code>sed</code> 版本不支持扩展的 regexps,请使用

<code>-r</code> 选项告诉 <code>sed</code> 您使用的是扩展语法。清单 14 展示了要对 <code>oursearch</code> 变量和

<code>sed</code> 命令进行哪些更改才能让扩展的正则表达式完成清单 13 中基本正则表达式完成的任务。

<a>清单 14. 使用扩展的正则表达式和                sed</a>

本文介绍了您可以使用正则表达式以及 <code>grep</code> 和 <code>sed</code> 对 Linux 命令行执行的操作,这还只是冰山的一角。使用手册了解更多有关这些高价值工具的信息。

<a href="http://www.ibm.com/developerworks/cn/linux/l-lpic1-v3-103-7/">http://www.ibm.com/developerworks/cn/linux/l-lpic1-v3-103-7/</a>

继续阅读