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

概述

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

  • 创建简单的正则表达式
  • 使用正则表达式搜索文件和文件系统
  • 使用正则表达式和 sed

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

先决条件

为了从本文获得最大的收益,您应该具备基础的 Linux 知识,并且具有一个能够正常运行的 Linux 系统,以便练习本文讨论的命令。不同版本的程序输出的结果的格式可能不同,因此您的结果可能与本文图片和清单所示的结果有所不同。本文以之前的文章 “学习 Linux,101:文本流和过滤器” 中讨论的概念为基础。


回页首

设置示例

联系 Ian

  Ian 是我们最受欢迎并且很多产的作者之一。查看 Ian 的个人资料 并与他和 My developerWorks 上的其他作者和读者联系。

在本文中,我们将使用文章 “学习 Linux,101:文本流和过滤器” 中建立的文件练习命令。如果您没有学习该文或者没有保存得到的文件,您可以在名为 lpi103-7 的子目录中新建一个子目录,并创建必要的文件。打开文本窗口,使用主目录作为当前目录。然后,将清单 1 中的内容复制到窗口运行命令,这将创建 lpi103-7 子目录以及您将使用的文件。

清单 1. 创建示例文件

mkdir -p lpi103-7 && cd lpi103-7 && {
echo -e "1 apple\n2 pear\n3 banana" > text1
echo -e "9\tplum\n3\tbanana\n10\tapple" > text2
echo "This is a sentence. " !#:* !#:1->text3
split -l 2 text1
split -b 17 text2 y;
cp text1 text1.bkp
mkdir -p backup
cp text1 backup/text1.bkp.2
}
			

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

清单 2. 创建示例文件——输出

ian@attic4:~$ mkdir -p lpi103-7 && cd lpi103-7 && {
> echo -e "1 apple\n2 pear\n3 banana" > text1
> echo -e "9\tplum\n3\tbanana\n10\tapple" > text2
> echo "This is a sentence. " !#:* !#:1->text3
echo "This is a sentence. " "This is a sentence. " "This is a sentence. ">text3
> split -l 2 text1
> split -b 17 text2 y; 
> cp text1 text1.bkp
> mkdir -p backup
> cp text1 backup/text1.bkp.2
> }
ian@attic4:~/lpi103-7$

回页首

正则表达式

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

正则表达式(也称为 “regex” 或 “regexp”)是描述文本字符串的一种方式或者一种模式,程序可以根据任何文本字符串匹配 该模式,以提供强大的搜索功能。grep(正则表达式处理程序的缩写)是 Linux 或 UNIX 程序员或管理员的标准装备,他们可以在文件搜索或命令输出中使用正则表达式。在文章 “学习
Linux,101:文本流和过滤器
” 中,我们介绍了 sed(流编辑器的缩写),这是使用正则表达式在文件或文本流中查找和替换文本的另一个标准工具。本文将帮助您更好地理解
grepsed 使用的正则表达式。使用正则表达式的另一个程序是 awk

结合本系列文章中的其他部分您会发现,整本书都是以正则表达式和计算机语言理论为基础的。更多建议请参见 参考资料

根据您对正则表达式的了解,您可能发现正则表达式语法与 “学习 Linux,101:文件和目录管理” 中讨论的通配符语法有类似之处。但这种相似之处只是表面现象。


回页首

基本的构建块

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

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

连接
连接两个正则表达式创建一个更长的正则表达式。例如,正则表达式 a 匹配字符串 abcdcba 两次,正则表达式
b 也是一样。但是,ab 将只匹配 abcdcba,而
ba
将只匹配 abcdcba
重复
Kleene * 或重复操作符将匹配 0 次或多次前一个正则表达式。因此像 a*b 之类的表达式将匹配以任何以
a
开头以 b 结尾的字符串,包括 b 本身。Kleene * 不用转义,因此希望匹配字面值星号(*)的表达式必须让星号转义。这里使用的 * 与通配符中使用的 * 不同,通配符中的 * 号匹配任何字符串。
交替
交替操作符(|)匹配前置或后置表达式。它必须匹配前一个或后一个表达式之一。在基本语法中它必须转义。例如,表达式 a*\|b*c 匹配由任何数量的
ab 组成(但不是同时)且以一个 c 结尾的字符串。同样,单个字符
c 也是匹配的。

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


回页首

搜索文件和文件系统

我们将之前的示例中创建的使用文本文件(参见 “设置示例”)。研究清单 3 中的示例。注意
grep 使用一个正则表达式作为参数,还有 0 个或多个要搜索的文件。如果没有给定文件,grep 将搜索 stdin,这让它成为一个可以在管道中使用的过滤器。如果没有匹配任何行,则
grep 没有输出,尽管可以测试它的退出代码。

清单 3. 简单的正则表达式

ian@attic4:~/lpi103-7$ grep p text1
1 apple
2 pear
ian@attic4:~/lpi103-7$ grep pea text1
2 pear
ian@attic4:~/lpi103-7$ grep "p*" text1
1 apple
2 pear
3 banana
ian@attic4:~/lpi103-7$ grep "pp*" text1
1 apple
2 pear
ian@attic4:~/lpi103-7$ grep "x" text1; echo $?
1
ian@attic4:~/lpi103-7$ grep "x*" text1; echo $?
1 apple
2 pear
3 banana
0
ian@attic4:~/lpi103-7$ cat text1 | grep "l\|n"
1 apple
3 banana
ian@attic4:~/lpi103-7$ echo -e "find an \ns* here" | grep "s\*"
s* here

从上例中可以看出,有时候会得到出乎意料的结果,尤其是使用重复的时候。您可能预期 p* 或者 pp* 能够匹配几个带
p 的字符串,但是 p*x* 能匹配文件的所有行,因为 * 操作符匹配
0 次或多次前一个正则表达式。

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

快捷键

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

+
+ 操作符类似于 * 操作符,但是它匹配一次或多次前一个正则表达式。基本表达式中它必须转义。
?
? 表示前一个表达式是可选的,因此它表示匹配 0 次或 1 次。这与通配符中使用的 ? 不同。
.
.(句点)是表示任何字符的元字符。最常使用的方式是 .*,该表达式匹配包含任何字符(或没有字符)的任意长度的字符串。不用说您就明白,这一般在一个较长的表达式中使用。比较句点与通配符中使用的 ?,.* 与通配符中使用的 *。

清单 4. 更多正则表达式

ian@attic4:~/lpi103-7$ grep "pp\+" text1 # at least two p's
1 apple
ian@attic4:~/lpi103-7$ grep "pl\?e" text1
1 apple
2 pear
ian@attic4:~/lpi103-7$ grep "pl\?e" text1 # pe with optional l between
1 apple
2 pear
ian@attic4:~/lpi103-7$ grep "p.*r" text1 # p, some string then r
2 pear
ian@attic4:~/lpi103-7$ grep "a.." text1 # a followed by two other letters
1 apple
3 banana

匹配一行的开始或结束

^(脱字符号)匹配一行的开始,$(美元符号)匹配行的结束。^..b 匹配行开始处任何后跟 b 的两个字符,而
ar$ 匹配任何以 ar 结束的行。正则表达式 ^$ 匹配空行。

更复杂的表达式

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

清单 5. 圆括号和字符类


ian@attic4:~/lpi103-7$ grep "\(an\)\+" text1 # find at least 1 an
3 banana
ian@attic4:~/lpi103-7$ grep "an\(an\)\+" text1 # find at least 2 an's
3 banana
ian@attic4:~/lpi103-7$ grep "[3p]" text1 # find p or 3
1 apple
2 pear
3 banana
ian@attic4:~/lpi103-7$ echo -e "find an\ns* here\nsomewhere." | grep "s[.*]"
s* here
ian@attic4:~/lpi103-7$ echo -e "find an\n * in position 2." | grep ".[.*]"
 * in position 2.

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

范围表达式(Range expression)
范围表达式是使用 -(连字符)分隔的双字符,比如数字里面的 0-9,十六进制里的 0-9a-fA-F。注意,范围与语言环境有关。
署名类(Named class)
有些署名类可以为通常使用的类提供便捷。署名类以 [: 开始,以 :] 结束,可以在括号表达式中使用。示例如下:

[:alnum:]
字母数字字符
[:blank:]
空格和制表符
[:digit:]
数字 0 到 9(等效于 0-9)
[:upper:] 和 [:lower:]
分别为大写字母和小写字母。
^(求反)
在字符类 [ 后的第一个字符使用时,^(脱字符号)对剩余字符求反,因此只有类中不存在该字符时(前导^除外)才能匹配。               

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

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

清单 6. 更多字符类

ian@attic4:~/lpi103-7$ # Match on range 3 through 7
ian@attic4:~/lpi103-7$ echo -e "123\n456\n789\n0" | grep "[3-7]"
123
456
789
ian@attic4:~/lpi103-7$ # Find digit followed by no n or r till end of line
ian@attic4:~/lpi103-7$ grep "[[:digit:]][^nr]*$" text1
1 apple
ian@attic4:~/lpi103-7$ # Find a digit, n, or z followed by no n or r till end of line
ian@attic4:~/lpi103-7$ grep "[[:digit:]nz][^nr]*$" text1
1 apple
3 banana

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

哪些内容匹配?

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

图 1. 使用颜色区分 grep 匹配内容

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


回页首

扩展的正则表达式

扩展的正则表达式语法是 GNU 扩展。我们在基本语法中使用时,它不需要转义一些字符,包括圆括号、'?'、'+'、'|'和 '{'。但缺点在于,如果您在正则表达式中将它们作为字符解释,那么必须进行转义。您可以使用
-E(或者 grep 的 --extended-regexp 选项)表示您正在使用扩展的正则表达式语法。此外,egrep 命令也可以帮助您实现这一点。清单 7 展示了本节上文中使用的示例,以及
egrep 使用的相应扩展表达式。

清单 7. 扩展的正则表达式

ian@attic4:~/lpi103-7$ # Find b followed by one or more an's and then an a
ian@attic4:~/lpi103-7$ grep "b\(an\)\+a" text1
3 banana
ian@attic4:~/lpi103-7$ egrep "b(an)+a" text1
3 banana

回页首

在文件中查找内容

现在您了解了基本的命令,让我们使用 grepfind 在文件系统中查找内容。示例相对比较简单;它们使用
学习 Linux,101:文本流和过滤器 中创建的文件或者您在 lpi103-7 目录及其子目录中创建的文件。(参见 “设置示例”。)如果使用本系列之前的文章中创建的文件,您将有一些额外的文件,因此将看到一些额外的结果。

首先,grep 可以一次搜索多个文件。如果添加 -n 选项,它将告诉您匹配的行号。如果只想知道匹配多少行,可以使用
-c 选项,如果只想获得匹配的文件列表,可以使用 -l 选项。清单 8 展示了一些示例。

清单 8. 搜索多个文件

ian@attic4:~/lpi103-7$ grep plum *
text2:9	plum
yaa:9	plum
ian@attic4:~/lpi103-7$ grep -n banana text[1-4]
text1:3:3 banana
text2:2:3	banana
ian@attic4:~/lpi103-7$ grep -c banana text[1-4]
text1:1
text2:1
text3:0
ian@attic4:~/lpi103-7$ grep -l pear *
text1
text1.bkp
xaa

查看清单 8 中的 -c 选项,您会看到一行 text3:0。                 您经常需要知道某个内容在文件中出现了多少次,但是不用知道没有出现该内容的文件。grep 命令有一个
-v 选项,它表示只显示匹配的行输出。因此,我们可以使用正则表达式 :0$ 查找以逗号和 0 结尾的行。

下一个示例是使用 find 查找当前目录及其子目录中的所有常规文件,然后使用 xargs 将文件列表传递到
grep,以确定每个文件中出现 banana 的次数。最后,通过再一次调用 grep 筛选该输出,这一次使用
-v 选项查找所有以 :0 结尾的行,只用告诉我们包含字符串 banana 的文件计数。

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

ian@attic4:~/lpi103-7$ find . -type f -print0| xargs -0 grep -c banana| grep -v ":0$"
./backup/text1.bkp.2:1
./text2:1
./text1:1
./yaa:1
./xab:1
./text1.bkp:1

回页首

正则表达式和                sed

文章 “学习 Linux,101:文本流和过滤器” 中介绍了 sed——流编辑器,其中提到 sed 使用正则表达式。regexps 可以在地址表达式和替代表达式中使用。

如果您需要查找某内容,那么只需要使用 grep。如果需要从匹配行中提取搜索字符串,或者相关字符串,那么需要进一步操作,您可以选择使用
sed。让我们解释一下它的工作方式。首先回忆我们的两个示例文件,text1 和                text2,其中包含了一个数字,后跟空格,再加一个水果的名称,而 text3 包含重复的语句。我们在清单 10 中再看一次它的内容。

清单 10. text1、text2 和 text3 的内容

ian@attic4:~/lpi103-7$ cat text[1-3]
1 apple
2 pear
3 banana
9	plum
3	banana
10	apple
This is a sentence.  This is a sentence.  This is a sentence.

首先,我们将使用 grepsed 提取以一个或多个数字开头,且后跟空白字符(空格或制表符)的行。一般情况下,sed 在一个周期结束时打印出每个行,因此我们使用 sed 的
-n 选项禁止输出,然后使用 sed 中的 p 命令只打印匹配我们正则表达式的行。要确认我们对这两个工具使用的正则表达式相同,我们将其赋予一个变量。

清单 11. 搜索 grep 和 sed

ian@attic4:~/lpi103-7$ grep "$oursearch" text[1-3]
text1:1 apple
text1:2 pear
text1:3 banana
text2:9	plum
text2:3	banana
text2:10	apple
ian@attic4:~/lpi103-7$ cat text[1-3] | sed -ne "/$oursearch/p"
1 apple
2 pear
3 banana
9	plum
3	banana
10	apple

注意,grep 在搜索到多个文件时将显示文件名称。因为我们使用 cat 提供 sed 的输出,所以
sed 无法知道源文件名。但是,匹配行是相同的,正如我们期望的那样。

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

清单 12. 使用 sed 删除前导数字

ian@attic4:~/lpi103-7$ cat text[1-3] | sed -ne "/$oursearch/s/$oursearch//p"
apple
pear
banana
plum
banana
apple
            

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

清单 13 展示了两种实现同一个任务的方式。首先,我们剔除了前导数字以及后面的空格,然后剔除第一个空格或选项卡之后的所有内容,并打印剩下的内容。在第二个示例中,我们引入了圆括号将整个行分为 3 个部分,数字和后面的空格、第二个字以及其他内容。我们使用
s 命令将整个行替换为第二个字,然后打印结果。您可以尝试变化一下方式,忽略第三部分,\(.*\),看看是否能解释发生了什么。

清单 13. 获取水果名

ian@attic4:~/lpi103-7$ echo "7 lemon pie" | cat - text[1-3] |
> sed -ne "/$oursearch/s/\($oursearch\)\([^[:blank:]]*\)\(.*\)/\2/p" |
> sort | uniq
apple
banana
lemon
pear

有些旧版本的 sed 不支持扩展的正则表达式。如果您的 sed 版本不支持扩展的 regexps,请使用
-r 选项告诉 sed 您使用的是扩展语法。清单 14 展示了要对 oursearch 变量和
sed 命令进行哪些更改才能让扩展的正则表达式完成清单 13 中基本正则表达式完成的任务。

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

ian@attic4:~/lpi103-7$ echo "7 lemon pie" | cat - text[1-3] |
> sed -nre "/$oursearchx/s/($oursearchx)([^[:blank:]]*)(.*)/\2/p" |
> sort | uniq
apple
banana
lemon
pear
plum
            

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

http://www.ibm.com/developerworks/cn/linux/l-lpic1-v3-103-7/

时间: 2024-10-14 16:41:56

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

学习 Linux,101: 自定义或编写简单脚本【转】

转自:http://www.ibm.com/developerworks/cn/linux/l-lpic1-105-2/index.html 学习如何使用标准的 shell 语法.循环和控制结构,以及成功或失败测试来自定义现有脚本或编写简单的新 bash 脚本.您可以使用本教程中的资料学习针对 Linux 系统管理员认证的 LPI 102 考试内容,或者仅为兴趣而学习. 查看本系列更多内容 | 0 评论 Ian Shields, Linux 作家, Freelance 2016 年 2 月 23

学习 Linux,101: 创建分区和文件系统

概述 在本文中,学习磁盘分区和 Linux 文件系统相关内容.学习: 创建分区 使用 mkfs 命令来设置 ext2.ext3.ext4.xfs.Reiser v3 和 vfat 文件系统 创建和管理交换空间 本文帮助您准备 Linux Professional Institute's Junior Level Administration (LPIC-1) 考试 101 中主题 104 下的目标 104.1.该目标的权值为 2. 注意:本文包含适用于 LPI Exam 101: Objecti

Linux基础之正则表达式,用户、组管理命令介绍_Linux

通配符(Globbing) 通配符与元字符类似,通配符主要用于文件名的匹配,而元字符则主要用在字符串的匹配上: 下面介绍几种常用的通配符: * 表示匹配任意位数的任意字符 ? 表示匹配一位任意字符 ^ 表示取反,不包含的意思 [] 表示此区间内的任意一个字符 {} 表示一种集合 \ 转义字符,使具有特殊意义的字符失去原有意义 | 表示'或',匹配一组可选的字符 元字符 元字符是用来描述字符的特殊字符. 常用的元字符及意义如下: * 重复前面的字符0次或者多次 . 匹配任意字符一次 \+ 匹配前面

如何学习linux的建议

  一.从基础开始 常常有些朋友在Linux论坛问一些问题,不过,其中大多数的问题都是很基的.例如:为什么我使用一个命令的时候,系统告诉我找不到该目录,我要如何限制使用者的权限等问题,这些问题其实都不是很难的,只要了解了Linux的基础之后,应该就可以很轻易的解决掉这方面的问题.而有些朋友们常常一接触Linux就是希望构架网站,根本没有想到要先了解一下Linux的基础.这是相当困难的. 二.Linux命令是必须学习 虽然Linux桌面应用发展很快,但是命令在Linux中依然有很强的生命力.Lin

linux发行版介绍及如何学习Linux

一. 选择适合自己的linux发行版 谈到linux的发行版本,太多了,可能谁也不能给出一个准确的数字,但是有一点是可以肯定的,linux正在变得越来越流行, 面对这么多的Linux 发行版,打算从其他系统转到linux系统来的初学者可能会感到困惑,即便是忠实的 Linux 用户也没有时间和精力去挨个尝试,因此初学者在学习linux的之前,需要有一个明确的方向,选择一个适合自己的系统开始学习linux至关重要!下面我们就分类介绍. 1.1 初学者入门首选-redhat系列 在学习redhat系列

由su和su -的区别谈学习linux运维方法

老男孩Linux培训新班刚开始,老男孩发现群里就在讨论这个su和su -的区别,有的同学们说,直接su就可以,有的说必须要su -.有的同学直接发问,到底su和su -有什么区别? 1授之以"鱼"的答案 6.6.1.3 su命令实例 当不加任何参数执行su命令时,表示要切换到root用户,但这样执行,会遇到一些问题.因为虽然是切换到root用户了,但并没有改变为root用户登录环境,用户默认的登录环境,可以在/etc/passwd 中查得到,包括家目录,shell类型等.比较规范的操作

浅谈Linux grep与正则表达式

grep简介 grep 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.通常grep有三种版本grep.egrep(等同于grep -E)和fgrep.egrep为扩展的grep,fgrep则为快速grep(固定的字符串来对文本进行搜索,不支持正则表达式的引用但是查询极为快速).grep是Linux文本处理三剑客之一. grep使用方式 使用方式: grep [OPTIONS] PATTERN [FILE...] grep [OPTIONS] [-e PATTERN

总结六条对我们学习Linux系统有用的忠告

接触linux需要的是端正自己的态度,这个玩意可不是一天两天就能拿得下的.学习个基础,能装系统.能装常见服务.能编译.能配置存储空间.能配置系统参数.能简单查看系统负载等基本够用.但这些只保证能做机房运维,真正和进阶的运维工作不在机房,真正的运维工作也不仅仅只是Linux.Linux只是基于Linux系统运行环境的基础知识,衡量一个好的Linux系统下运维工程师也不一定非得用Linux知识的深浅,当然Linux钻研得越深越好. 还要看工作内容,就拿我来说作为一个机房运维维护人员,机房运维分很多种

Linux基础命令:搜索文件

我们先来学习一下如何搜索文件,特别是刚开始学习Linux的时候,自己建立的文件不知道放在哪里了,常有发生.如果知道文件名,却不知道文件在那个目录下面了,我们就可以使用locate命令来搜索文件.看如下操作: [root@Linux one]# locate install.log/root/install.log/root/install.log.syslog 看一下,我们一下就搜索了两个与install.log相关的文件,他们都在/root目录下,同时我们感觉到,使用这个命令搜索文件的速度比较