Linux系统 - 文本处理

其实Linux系统总是倾向于文本的,至于为什么本书在后面还有更深入的介绍。但是要证明我们所说的内容不虚,就得给你们看看Linux处理文本的本事。

1.1.1             查看文本内容

在上一章我给你讲了一个有关命令行的罗曼蒂克故事,里面涉及到了文本内容查看的操作。在Linux 系统上查看文件的方法很多,包括cat、more、less、head和tail 等,其中 tail 命令的作用我向你好好交待一下。它不仅可以用于查看文件的最后若干行的内容(默认为最后10行),还可以实现不停地读取和显示文件内容。这样做有什么好处呢?当然有,它可以用于查看日志起到实时监视的作用。

1.1.2             正则表达式

其实Linux系统中处理文本的工具还有很多,功能也远比我们所能想象到的任何工具都要强大。要想真正掌握它们,就必须要掌握一项基本技能——正则表达式。

正则表达式已经无处不在了,无论是网络文章,还是很多图书,亦或是教材也都在讲解正则表达式,我们也坚信正在读本书的你也早已熟稔正则表达式的运用。但是,我们依然要介绍它,只是换了一个角度,以期望初学者能有一个好的开端,行家里手们能够加深入的理解它,因为要熟练使用Linux是离不开正则表达式。

对于初学者来讲,一定会追问到底什么是正则表达式。这是一个很难回答的问题。但是我们可以通过一个应用实例来让大家明白。一般地,我们在处理字符串的时候后有需要判断是否存在某个子串的情况,比如要在字符串“prefix=/usr”中来判断是否拥有“prefix=”这个字串,可以使用逐一比对的方式来完成这个功能。这看起来很好,而且屡试不爽。还能进一步演进算法,实现提取诸如“prefix”的“值”这样的需求。显然只需要判断字符串中有“prefix=”这个子串并确认它的位置就能够提取出“/usr”这个子串了。看来变量取值算法也是很“简单”的啊!但是别骄傲,当你遇到“exec-prefix=/libexec prefix=/usr”这样的字符串时,你怎么取“prefix”的值呢?你会说,稍微变化一下算法就行了。那遇到的字符串更复杂怎么办?对于这类需求,我们可以变换一下思路,即找到“K=V”这样形式的子串会更好。或许现在你觉得思路又开阔了。但是别着急,还有更复杂的在等着你。比如字符串“3.1416 * 100 ^ 2”,要提取出所有数字。似乎你又有思路了,无外乎提取连续的0至9,附带+、-号以及“.”的字串,但是这就能完成任务了?如果再变成“3.1416 * 1.3E2 ^ 2”这样了呢?反正变化很多。如果你要还是保持前面的思路,我保证你使用几十万行代码的if-else都满足不了需求。估计你现在一定会想,如果有一门语言只需要编写少量的代码就能够满足上面的所有需求的话,一定要学会它。这样你就不怕老板在这个地方的各种刁难。那我告诉你,还真有一门这样的语言,它就是——正则表达式。一些简单的正则表达式,就能够满足上面的这些需求,比如:

prefix=

[ \t]*[a-zA-Z]+=[ \t]*[a-zA-Z/]+[ \t]*

[+-]?[0-9]+(\.[0-9]*)?([Ee][+-]?[0-9]+)?

我们说上面的这些方方块块、花花草草就是正表达式了,它们具体都是什么含义呢?

为了搞清楚这个问题,我们首先要对正则表达式所要处理的文本进行一下精确定义。这个定义是:文本是指字符串的集合,其中的字符来自于一个有限的字符集合。也就是说,文本是由一个有限的字符集构成的,但是文本本身既可以是有穷集合,也可以是无穷集合。就比如属于文本的源代码文件,就是满足某种语言语法的全体字符串的集合,但是不同的源代码全部算在一起显然就是一个无穷的集合。当然,也可以有非常简单的文本,比如只含有一个字母“a”的文本,如果用集合了表示的话就是{a}。按照这个定义,正则表达式就是来描述任意文本的一种特殊表达式,而且拥有两个基本要素:

l  表达式ε表示一个文本,仅包含一个长度为0的字符串,也可以理解为{NULL}。通常将NULL记作ε;

l  对字符集中任意字符a,表达式a表示仅有一个字符a的文本,即{a}

以及三种基本元算规则:

l  两个正则表达式的并,记作X|Y,表示的文本是正则表达式X所表示的文本与正则表达式Y所表示的文本的并集。比如a|b所的的文本就是{a,b},类似于加法;

l  两个正则表达式的连接,记作XY,表示的文本是将X文本中的每个字符串后面连接上Y文本中的每一个字符串之后,再把所有这种连接的结果组成一种新的文本。比如X=a|b,Y=c|d,那么XY所表示的文本就是{ac,bc,ad,bd}。因为X是{a, b},而Y是{c,d},连接运算取X文本的每个字符串接上Y文本的每一个字符串,最后得到了4种连接结果。这类似于乘法;

l  一个正则表达式的克林闭包,记作X*,表示分别将0个、1个、2个……n个X与自己连接,然后再把所有这些求并。也就是说X*=ε|X|XX|XXX|……。比如a*这个正则表达式,就表示的是无穷文本{ε,a,aa,aaa,……}。这相当于任意次重复一个语言。

以上三种运算写在一起时,克林闭包的优先级高于连接运算,而连接运算的优先级高于并运算。这就是正则表达式的全部规则。完全不难理解吧?

在正则表达式的实际使用中,如果只是提供上述三种运算很多时候会使得正则表达式被书写的十分复杂,为了简化正则表达式又引入了一些扩展运算。这些扩展运算都是基于三种基本运算的,它们是:

l  []方括号表示括号内的字符做并运算,同时支持范围描述符“-”。比如[abcd]就等于a|b|c|d,等价于[a-d]。

l  由于方括号中支持范围描述“-”,如果要使用“-”字符,则需要将它放在方括号的开头,如[-abc]等于-|a|b|c。

l  方括号中以^字符开头,表示在字符集中配出方括号中的所有字符之后,所剩字符的并运算。比如[^ab]则表示除了ab以外的所有字符求并。

l  X?表示X|ε。这就代表X与空字符串之间可选。

l  X+表示XX*。这等于限制了X至少要重复1次。

现在我们就可以理解一下我们前面给出的这几个正则表达式了。

第一个正则表达式是“prefix=”,这就是一个连接运算,由“p”、“r”、“e”、“f”、“i”、“x”、“=”这几个字符连接而成。

第二个正则表达式则是提取出“K=V”这样形式的子串,取“=”号右侧子串就等同于取值了。而且限定了用空格来区分“K=V”结构的边界。[ \t]就是描述空格的正则表达式。

第三个正则表达式是提取数字,包含浮点数和科学计数法。比较困惑的是“()”圆括号的出现。这是因为很多正则表达式工具默认都使用单一字符作为字串边界,为了扩大子串的边界,可以使用“()”来明确限定,所以也被成为子表达式。但是这种子表达式并不属于标准正则表达式的范畴,所以又遇到不支持的情况。

好了,这部份的内容就算结束了,这是纯理论的,希望这些内容能够帮助到大家。本书的后续内容还会遇到一些有关正则表达式的内容,那些就更加偏重于实践了。本书之所以要这样组织,主要是考虑到正则表达式的派系过于繁杂,不同情况下所使用的工具对正则表达式派系的支持可能不太一样,只有遇到合适的场景才能更好的体会的这些差异来。其实接下来的内容就已经是这样的了。

1.1.3             搜索文本的grep

grep就是一个使用正则表达式来检索文件内容的强大工具,是每一个Linux用户必练之神兵。

grep的全称是global search regular expression(RE)and print outthe line,翻译过来就是全面搜索正则表达式并把行打印出来。其实grep并不是一个单独的程序,而是一个家族。包括grep、egrep和fgrep。egrep和fgrep在使用上与grep并没有显著的不同。egrep是grep的扩展,支持更多的正则表达式元字符;fgrep就是fixed grep或fast grep,它实际上只是搜索字符串而并不使用正则表达式,所以诸如$、*、[、]这些正则表达式中的元字符只会被fgrep解释为其字面含义。Linux中所使用的是GNU版本的grep,可以直接通过-G、-E和-F命令选项来使用grep、egrep和fgrep的功能。

grep的工作方式是这样的,它在一个或多个文件中搜索字符串模版。字符串模版使用正则表达式来描述。如果模板包括空格,则需要使用引号括起来。模板后的所有字符串被看作文件名。搜索的结果被送到标准输出,并不影响文件内容。就因为它能把搜索的结果输出的标准输出中,同时还能从标准输入中搜索,所以结合管道来使用,功能将非常强大。

grep所支持的正则表达式如表2-8所示:

表2-8

元字符

匹配对象

基本集

^

表示句首。如^abc表示以abc开首的句子

$

表示句尾。如abc$表示以abc结尾的句子

*

表示前一个字元集的出现次数为0或多次。如ab*c表示a与c之间可有0或多个b存在

[ ]

匹配一个指定范围内的字符,如^[0-9]表示以数字开始的行

[^]

匹配一个不在指定范围内的字符,如\'[^(D-F)level]\ '表示匹配不包含D-F的一个字母开头,紧跟level的行

(...)

匹配一个不在指定范围内的字符,如\'(magic)\',magic被标记为1

\<

表示词首。如 /<abc 表示以 abc 开首的词

\>

表示词尾。如 /<abc 表示以 abc 结尾的词

1.1.4             排序和去重

我们会遇到这么一个需求,利用ps aux查看进程后对显示的进程按CPU或memory进行排序。这时候排序命令就可以派上用场了。除此之外,还可以利用排序命令对日志文件进行排序和去重然后再进行分析,例如从错误日志中统计找出已经发生过哪些类型的错误,各个错误发生的排行榜,从访问日志中统计得出用户访问次数等。总之,排序和去重的用途很广泛,我们先来看看下面的例子。

cut -d' ' -f1 ~/.bash_history|sort -d | uniq -c|sort -nr|head

这行脚本的功能是统计出你最常用的十条历史命令及其使用次数

| 是表示管道,把一个命令的输出传递给另一个命令作为输入。我们在shell脚本编写那章会详细讲解。阅读脚本命令首先要把一组命令拆解成一个一个命令,上面这行脚本可以分解成:

l  cut -d' ' -f1 ~/.bash_history

从~/.bash_history文件中以空格为分隔符(-d' ')剪出第一列(-f1,如果要剪出多列,则为-f1,3等)。

l  sort –d

按字典序(-d)排序剪出来的第一列。

l  uniq –c

对文本去重,并统计次数(-c)。我们用到了sort和uniq两个命令。实际上他们经常需要联合起来使用。uniq去重功能,只能针对连续的多行进行去重,只剩下唯一的一条。不排序的话,不连续的重复行就去掉不了。

l  sort –nr

按数字排序(-n)并按降序(-r)排列,没有-r 默认是升序排列。

l  head

输出显示文件前面部分的内容,默认显示前面10行的内容。

1.1.5             文件对比

搭建网站离不开数据库,在Linux系统上我们使用源码安装了MySQL服务器。不久我们发现 google发布了一系列MySQL补丁,这些补丁很好很强大,这时候我们迫不及待地要把新东西更新到系统上,那就用patch命令来打补丁吧。喝水不忘打井人,那补丁又是怎么做的呢?这个任务就需要文件对比命令diff来帮忙。

diff用来比较两个文本文件的差异,是代码版本管理的基石之一。diff采用动态规划算法实现差异比较,此算法的基础是最长公共子序列,特复杂的,有兴趣地话可以读读科曼的《算法导论》。

diff提供了正常(normaldiff)、上下文(contextdiff)和合并(unifieddiff)三种格式告诉你两个文件的差异。正常格式的diff不太容易读,上下文格式的diff在文件相似度比较高的情况下会显示大量重复的内容,很浪费空间。1990年,GNU diff率先推出了“合并格式”的diff,使用类似diff -urfile01_old file01_new> file01.patch命令就可将变动前和变动后的文件的上下文合并在一起显示。我举个例子咱们一起看看执行完diff -ur后产生的patch文件。

72    === modified file'configure.in'

73    --- configure.in    2011-06-06 13:53:46 +0000

74    +++ configure.in    2011-07-27 21:10:40 +0000

75    @@ -361,15 +361,11 @@

76   

77    if test "$GCC"= "yes"

78    then

79    -  # mysqld requires -fno-implicit-templates.

80    -  # Disable exceptions as they seams to createproblems with gcc and threads.

81    # mysqld doesn't use run-time-type-checking,so we disable it.

82    # We should use-Wno-invalid-offsetof flag to disable some warnings from gcc

83    # regarding offset()usage in C++ which are done in a safe manner in the

84    # server

85    -  CXXFLAGS="$CXXFLAGS-fno-implicit-templates -fno-exceptions -fno-rtti"

86    - AC_DEFINE([HAVE_EXPLICIT_TEMPLATE_INSTANTIATION],

87    -    [1], [Defined by configure. Use explicittemplate instantiation.])

88    +  CXXFLAGS="$CXXFLAGS -fno-rtti"

89    fi

90   

91    MYSQL_PROG_AR

 

73-74行是文件的基本信息。

--- configure.in    2011-06-0613:53:46 +0000

+++ configure.in    2011-07-2721:10:40 +0000

“---”表示变动前的文件,“+++”表示变动后的文件。

75行处变动的位置用两个@作为起首和结束。

  @@ -361,15 +361,11 @@

“-361,15”分成三个部分:减号表示第一个文件,“361”表示第361行,“15”表示连续15行,也就是表示下面是第一个文件从第361行开始的连续15行。同样道理,“+361,11”表示第二个文件从第361行开始的连续11行。

从 patch 文件的76行开始除了显示有变动的那些行外,还把这两个文件的上下文合并显示在一起。每一行最前面是标志位,空表示无变动,减号表示第一个文件删除的行,加号表示第二个文件新增的行。

应用patch命令打补丁,命令虽不复杂,也是考验人品的机会,有时候不是打一次patch就成功完成软件更新的。首先要选择好更新软件相同版本的补丁,版本不对的话patch是打不上去的,例如google patch只能加在某些版本的MySQL之上。其次确定是否需要逐层打patch,例如google patch的 GlobalTransactionIds功能包含在patch v3内,但是如果只打patch v3的话,编译是通不过的,没办法只能一层一层打patch(v1->v2->v3)。还有经常会遇到应用补丁到整个目录的,别忘了设置p级别,也就是在补丁文件里需要打补丁的文件在你电脑上的路径名跟在创建补丁的电脑上可能不同,p级别告诉patch命令忽略掉路径名的几个部分以正确的识别文件。例如,如果你在demo.patch文件里看到这样一个文件名:

/home/fangru/mysql-test/extra/rpl_tests/rpl_foreign_key.test

而你当前正工作在mysql-test/extra/rpl_tests/rpl_foreign_key.test,我们来算一下p级别,方法是计算从路径最开始删除的每个路径分隔符直到剩下的部分存在于你的工作目录的分隔符数目。在这个例子中p应为3,所以需要使用patch –p3 < demo.patch。在出现不匹配的情况时,patch会生成rej文件,你可以手工修改记录失败的地方。

除了上面谈到这些命令,Linux 还拥有cut、wc、tr、split和join等众多文本处理命令,活学活用,勤查手册才是硬道理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值