(022) Linux之文本处理

十年运维系列之基础篇 - Linux

作者:曾林 

联系:1494445739@qq.com

网站:www.jplatformx.com

版权:文章未经同意请勿转载


一、引言

      由于所有类UNIX操作系统都严重依赖于文本文件来进行某些数据类型的存储,所以需要很多可以进行文本操作的工具。本章节主要介绍一些与“切割”文本有关的命令。

 

二、cat——进行文件之间的拼接并且输出到标准输出

      cat命令有许多有趣的参数选项,而其中多数则是用于提高文本内容的可视化效果。比如参数-A选项就是这样的一个例子,它用于显示文本中的非打印字符。例如,用户有时想看一下文本中是否嵌入了控制字符,其中最常见的就是制表符(而不是空格)以及回车符,在MS-DOS风格的文本文件中,回车符经常作为结束符出现。另一种常见情况是文件中包含末尾带有空格的文本行。

      我们创建了一个测试文件,用cat程序作为一个简单的文字处理器。为此,只需要输入cat命令(随后指定了用于重定向输出的文件)再输入文本内容,按Enter键结束行输入,最后按下Ctrl+D告诉cat到达文件末尾。下例中,我们输入了一个以Tab制表符开头、空格符结尾的文本行。

      输出结果表明,文本中的Tab制表符由符号“^I”表示。这是一种常见的表示方法,意思是“Ctrl+I”,结果证明,它等同于Tab制表符。同时,在文件末尾出现的$表示换行符。

     MS-DOS文本与UNIX文本的比较。我们利用cat查找文件中的非打印字符,原因之一是cat可以发现隐藏的回车符。而这些隐藏的回车符来自哪里呢?当然是DOS和Windows。UNIX和DOS在文本文件中定义每行结束的方式并不相同。UNIX换行符(ASCII吗10)作为行末尾,而MS-DOS系统及其衍生系统则使用回车符(ASCII码13)和换行符共同作为行末尾。

      有几种方式可以将文件从DOS格式转换为UNIX格式。许多Linux系统,都自带dos2UNIX和UNIX2dos程序,它们用于DOS和UNIX格式之间的相互转换。然而,即便系统中没有dos2UNIX程序也没关系,DOS格式转换为UNIX格式的过程非常简单,只要将多余的回车符删除就可以。

      cat也有很多用于修改文本的参数选项。最著名的两个选项:-n,对行编号;-s,禁止输出多个空白行。示例如下图:

      本例中,创建了一个foo.txt测试文件的新版本,该文件内容为两个文本行,并以空白行隔开。用cat和-ns选项对其进行操作之后,多余的空白行便被移除、并对剩余的行进行了编号。

 

三、sort——对文本行进行排序

      sort是一个排序程序,它的操作对象为标准输入或是命令行中指定的一个或多个文件后将结果i送至标准输出。与cat用法类似,如下所示,我们将直接使用键盘演示标准输入内容的处理过程。

      输入sort命令之后,输入字母c、b、a,最后按下ctrl+D结束输入。然后查看处理结果,会发现这些行都以排好的顺序出现。

      由于sort命令允许多个文件作为其输入参数,所以可以将多个文件融合为一个排序的整体文件。我们可以使用如下的命令去执行:

      shell> sort file1.txt file2.txt file3.txt > final_sorted_list.txt

     此外,sort也有一些有趣的选项,如下图所示:

选项 全局选项表示 描述
-b --ignore-leading-blank 默认情况下,整个行都会进行排序操作,也就是从行的第一个字符开始。添加该选项后,sort会忽略行开头的空格,并且从第一个非空白字符开始排序
-f --ignore-case 排序时不区分字符大小写
-n --numeric-sort 基于字符串的长度进行排序。该选项使得文件按数值顺序而不是按字母表顺序进行排序
-r --reverse 逆序排序。输出结果按照降序排序而不是升序
-k --key=field1[.field2] 对field1和field2之间的字符排序,而不是整个文本行
-m --merge 将每个输入参数当做已排好序的文件名。将多个文件合并为一个排好序的文件,而不执行额外的排序操作
-o --output=file 将排序结果输出到文件而不是标准输出
-t --field-separator=char 定义字段分隔符。默认情况下,字段由空格或制表符分开的。

     尽管以上多数选项的作用都易从其字面意义中看出,但也有一些例外,用于数值排序的-n选项就是这样的一个例子。该参数选项可以使sort根据数值进行排序。作为演示,我们可将du命令的输出结果进行排序,以确定最大的硬盘空间用户。正常情况下,du命令会列出一个路径名顺序排列的列表。如下图:

      以上的例子是sort针对数值进行的排序,但是如果我们碰到这样的情况,即如果我们想要基于文本行中某个数值来进行排序,又改怎么做呢?例如使用ls -l这样的命令输出如下的结果:

      此刻,忽略ls命令自有的根据文件大小排序的功能,而用sort程序依据文件大小进行排序,使用了命令行参数选项k来实现针对指定域的数字进行排序。

      sort的许多用法都与表格数据处理有关,比如上面的ls命令的输出结果。如果我们把数据库这个术语应用到上面的表格中,我们会说每一行就是一项记录,而每一个记录又包含多个字段,诸如文件属性、连接数、文件名、文件大小等。sort能够处理独立的字段,在数据库术语中,我们可以指定一个或多个关键字字段作为排序的关键值。在上面的例子中,指定了n和r选项来进行数值的逆序排序,并指定-k 5让sort程序使用第5个字段作为排序的关键值。

      k这个参数选项非常有趣,并且有很多的特性,但是首先我们需要了解sort是如何定义字段的。让我们来考虑一个非常简单的文本文件,它只有一行,并且该行只包含了该作者的名字。

      William Shotts

      默认情况下,sort程序会把该行看作有两个字段。第一个字段包含“William”字符串,第二个字段则是“Shotts”。这意味着空白字符(空格和制表符)用作字段之间的定界符。

      让我们来考虑一下用下面的文件。该文件包含从2006年~2008年三款流行的Linux发行版的发行历史。文件每行都有3个字段:发行版本名、版本号和MM/DD/YYYY格式的发行日期。使用文本编辑器将其保存,并取名为sort.txt。文件内容如下图所示:

      接下来我们使用sort命令来查看一下输出结果:

      输出排序中大部分都是正确的。 但是在Fedora的版本号排序时却出现了问题。以为字符集中,字符1是在5之前的。也就是说版本号字段是按照字符集排序而不是按照数字排序。

      为了解决这个问题,我们必须依据多个键值进行排序。首先我们需要对第一个字段进行字母排序,然后再对第三个字段进行数值排序。sort支持-k选项的多个实例,所以可以指定多个排序键值。事实上,一个键值可能是一个字段范围,如果没有指定任何范围,sort会使用一个键值,该键值始于指定的字段,一直扩展到行尾。如下便是使用多键值进行排序的语法。具体如下图:

      虽然为了清晰,我们使用了选项的长格式,但是-k 1,1 -k 2n格式是等价的。在第一个key选项的实例中,指定了一个字段范围。因为我们只想对第一个字段排序,所以指定了“1,1”,它意味着“始于并且结束于第一个字段”。在第二个实例中,我们指定了2n,表示“第二个字段是排序的键值,并且按照数值进行排序”, 一个选项字母可能包含在一个键值说明符的末尾,用来指定排序的种类。这些选项字母与sort命令的全局选项一样:b(忽略开头空白符)、n(数值排序)、r(逆序排序)等。

      以上列表的第三个字段包含的日期形式并不利于排序。在计算机中,日期通常以YYYY-MM-DD的形式存储,以方便按时间顺序排序,但该文本中的时间则是以美国形式MM/DD/YYYY存储,那么,该如何对时间进行排序呢。

      sort提供了一种解决方案。sort的key选项允许在字段中指定偏移,所以我们可以在字段内定义键值。如下图所示:

      通过指定-k 3.7,我们告诉sort从第三个字段的第7个字符开始排序,也就是从年份开始排序。同样,指定-k 3.1和-k 3.4选项以区分日期中的月和日,另外我们利用了n、r选项进行逆序数值排序。同时添加的b选项用来删除日期字段中开头的空格(行与行之间的空格字符数量不同,因此会影响排序结果)。

      有些文件并不是使用制表符或者是空格来作为字符定界符,例如这个/etc/passwd文件。如下所示:

     该文件的字段之间以冒号(:)作为分界符,那么该如何利用关键字段对此文件进行排序呢?sort提供了-t选项来定义字段分隔符,根据passwd文件的第7个字段内容进行排序(用户默认的shell环境),如下图所示:

 

四、uniq——通知或省略重复的行

      与sort相比,uniq是一个轻量级的命令。uniq执行的是一个看似简单的任务,给一个已经排好序的文件(包括标准输入)后,uniq会删除任何重复的行并将结果输出到标准输出中。它通常与sort结合使用以删除sort输出内容中重复的行。

      虽然uniq是一个与sort一起使用的传统的UNIX工具,但是GNU版本的sort同样支持-u选项,以用于移除sort输出内容中的重复行。

      创建一个文本文件以验证此特性。如下图所示:

      不要忘记按下ctrl+D以结束标准输入。此刻,如果运行uniq,文件内容并没有太大改动,重复的行也不会被删除。如下图所示:

      原因是uniq只有对已经排好序的文本才有作用。这是因为uniq只能移除相邻的重复行。如下图所示:

      常见的uniq选项如下表所示:

选项 功能描述
-c 输出重复行列表,并且重复行前面加上其出现的次数
-d 只输出重复行,而不包括单独行
-f n 忽略每行前n个字段。字段之间以空格分开,这与sort类似,但是与sort不同的是,uniq没有提供参数设置可选择的字段分隔符
-i 行与行之间比较时忽略大小写
-s n 跳过(忽略)每行的前n个字符
-u 仅输出不重复的行。该选项是默认的。

      使用uniq的-c选项,可输出文本中重复行的数量。实例如下图:

 

五、切片与切块

      下面讨论的3个命令,它们的作用都是剥离文本文件的列,并将它们以期望的方式重组。

  1. cut——删除文本行中的部分内容

      cut命令用于从文本行中提取一段文字并将其输出到标准输出。它可以接受多个文件和标准输入作为输入参数。下表展示cut命令选项:

选项 功能描述
-c char_list 从文本行中提取char_list定义的部分内容。此列表可能会包含一个或更多冒号分开的数值范围
-f field_list 从文本行提取field_list定义的一个或多个字段。该列表可能会包含由冒号分隔的一个、多个字段或字段范围
-d delim_char 指定-f选项后,使用delim_char作为字段分界符。默认时,字段必须以单个Tab制表符分开
--complement 从文本中提取整行,除了那些由-c和/或-f指定的部分

      下图展示了使用cut命令来提取指定的域。首先利用cat -A命令查看到该文件都是用制表符来作为分隔符的,也就是说字段之间仅仅只有单个的制表符,所以可以使用-f选项提取字段内容。

      由上图可知distro.txt文件是以制表符作为分界符的。所以用cut提取字段而不是字符再合适不过。这是因为用Tab作为分界符的文件,一般每行不会包含相同的字符数,所以计算字符在行内的位置很困难或者是根本不可能。然而在上例中,我们已经提取好了包含相同长度数据的字段,从而可以拿此字段作为字符提取实例。如下图所示:

      对输出结果再进行一次cut操作,便可以将字段中与年份相对应的第7至第10个字符提取出来,命令行中的符号“7-10”指范围。

      当处理字段时,我们可以指定非Tab字符作为分界符。如下所示例子演示的是从/etc/passwd文件中提取了每行的第一个字段。

      此处我们还使用了-d选项指定冒号作为字段的分界符。

      2. paste——合并文本行

      paste命令是cut命令的逆操作,它不是从文本文件中提取列信息,而是向文件中增加一个或是更多的文本列。该命令读取多个文件并将每个文件中提取出的字段结合为一个整体的标准输入流。与cut类似,paste也可以接受多个文件输入参数和标准输入。至于paste命令是如何运行的,我们可以通过下面的例子来进行了解。如下所示:我们描述了一个使用paste对distros.txt文件按照发行版本的时间顺序而排序的例子。

      首先,使用sort命令按照日期来排序distros.txt文件,并将排序后的结果储存于文件distros-by-date.txt文件中。如下图所示:

      接下来,我们使用cut命令来提取文件中前两个字段,并将结果存于文件distro-versions.txt中。如下图所示:

      接着就是提取发行版本日期,并将结果存于distros-dates.txt文件中。如下图所示:

      最后用paste命令来将distros-dates文件和distros-version文件进行合并,然后输出到标准输出中来,如下图所示:

      3. join——连接两文件中具有相同字段的行

      从某种程度上来说,join和paste类似,因为它也是向文件增加列信息,只是实现方式有些许不同。“join”操作通常与“关系数据库”联系在一起,它在关系数据库中把共享关键字段多个表格的数据组合成一个期望结果。“join”是一个基于共享关键字段将多个文件的数据拼接在一起的操作。

      至于join命令在关联数据库中是如何操作的,我想大家一定知道。示例如下,假定有一个比较小的包含两个表格的数据库,并且每个表都只包含一条记录。第一个表格,叫做customers,它有3个字段,分别为顾客号码(ID)、顾客的姓(FNAME)以及顾客的名(LNAME)。

ID FNAME LNAME
1 John Smith

      第二个表格叫做ORDERS,包含4个字段,它们是订单号(ORDERID)、顾客号码(ID)、数量(QUAN)以及顾客内容(ITEM)。

ORDERID ID QUAN ITEM
1 1 8 GUITA

      请注意,两个表都含有公共字段ID。这很重要,因为这就是两个表格之间的联系。

      join操作完成了两个表格中的字段结合,从而得到了有用的输出结果。关联的结果如下:

FNAME LNAME QUAN ITEM
John Smith 8 GUITA

      以上是关于关系型数据库中两表关联的阐述。以下介绍join命令。 因为两者思路雷同。使用方法如下图:

 

六、文本比较

      比较文本文件通常很有用,尤其对系统管理员以及软件开发者而言。例如,一个系统的管理者可能需要将已存在的配置文件与原来的配置文件相比较,以检查出系统漏洞,于此类似,程序员也会经常需要查看程序代码所经历的变化。 

  1. comm——逐行比较两个已排序的文件

      comm命令一般用于文本文件之间的比较,显示两文件中相异的行以及相同的行。下图建立两个几乎一模一样的文件,并且使用comm命令来比较两个文件的差异。

      从以上的结果可以看出,comm输出了三列内容。第一列显示的是第一个文件中独有的行,第二列展示的第二个文件独有的行,第三列展示的是两个文件共有的行。comm命令还支持-n形式的参数选项,此处的n可以是1,2或者3,使用时,它表示省略第n列的内容。例如,如果只想显示两个文件的共有行,只需要使用如下的命令即可:

      shell> comm -12 foo1.txt foo2.txt

 

      2. diff——逐行比较文件

      与comm命令类似,diff用于检测文件之间的不同。然而,diff比comm更复杂,它支持多种输出形式,并且具备一次性处理大文件集的能力。diff通常被软件开发者用于检查不同版本的源代码之间的差异,因为它能够递归检查源代码目录(通常称为源树)。diff的常见用法就是创建diff文件和补丁,它们可以为诸如patch这样的命令所用,从而实现一个版本的文件更新为另一个版本。

      diff命令的输出格式有三种,分别为默认格式、上下文格式(context)、统一格式。一般我们会使用上下文格式和统一格式。统一格式演示如下图:

      该结果以两个文件的名字和时间信息开头,第一个文件用星号表示,第二个文件用破折号表示。输出结果的其余部分出现的星号和破折号则分别表示各自所代表的文件。其他的内容便是两个文件之间的差异组。第一组差异,以*** 1,4 ***开头,表示第一个文件中的第一行至第四行;第二组便以--- 1,4 ---开头,表示第二个文件的第一行至第四行。每个差异组的行都是以如下四个标识符之一开头。如下表:

标识符 含义
(空) 该行表示上下文文本。表示两个文件共同的行
- 缺少的行。指此行内容只在第一个文件中出现,第二个文件中则没有
+ 多余的行。此行内容只有第二个文件才有,第一个文件则没有。
! 改变的行。两个版本的行内容都会显示出来,每一个都各自出现在差异组的相应的部分

      统一格式与上下文格式相似但是更简明,此格式用-u选项指定。如下图所示:

      上下文格式和统一格式之间最显著的区别就是,统一格式下没有重复的文本行,这使得统一格式比上下文格式更精简。上例中,也输出了上下文格式中出现的文件时间信息,并且后面紧跟“@@ -1,4 +1,4 @@"字符串,它表示差异组中描述的两个文件各自的范围。此字符串之后便是行本身,其中包含默认的三行文件内容。如下表所示:

字符 含义
(无) 两个文件共有的行
- 相对于第二个文件而言,第二个文件中没有的行。
+ 第一个文件没有的行

 

      3.  patch——对原文件进行diff操作

      patch命令用于更新文本文件。它利用diff命令的输出结果将旧版本的文件升级为较新版本。下面看一个众所周知的例子,Linux内核是由一个很大的、组织松散的志愿者团队开发的,其源代码处于持续不断更新中。Linux内核包含了几百万行代码,所以相比而言,某位开发成员每次所做的改变是如此微不足道。因此,对于每位开发者而言,每次代码改动一次就得向其他开发者发送整个内核源代码树是多么不切实际。事实上,一般只要发送diff补丁即可,diff补丁的内容是内核从较旧版本转变为较新版本所经历的改变,接受者然后使用patch命令将这些改变应用于其自身的源代码树。diff/patch有两个重要的特点:

  • 与源代码树相比,diff文件很小
  • diff文件非常简洁的描述了文件所做的改变,便于补丁的接收者快速对其进行评价

      当然,diff/patch不仅仅局限于源代码,它适用于任何文本文件。因此,它同样适用于配置文件以及其他文本文件。

      生成供patch使用的diff文件,GNU文件系统建议采用如下方式使用diff。

      shell> diff -Naur old_file new_file > diff_file

      此处old_file和new_file既可以是单独的文件也可以是包含文件的目录,使用-r选项是为了进行递归目录树搜索。

      一旦创建了diff文件,便可以将其用于修补原文件old_file,从而升级为新文件new_file。语句如下:

      shell> patch < diff_file

      以前面的测试文件为例子,做下图所示:

      本例中,我们创建了一个叫做foo.patch的diff文件,然后使用命令patch进行了修补。请注意该命令行,并没有给patch命令指定目标文件,因为diff文件(统一形式)已经在页眉中包含了文件名。可以发现,进行修补后,foo1.txt和foo2.txt一致了。也就是旧文件和新文件已经一致了。

      patch有很多参数选项,而且有很多额外的工具命令可以分析并编辑这些补丁文件。

 

七、非交互式文本编辑

      之前描述的文本编辑大多都是交互式的,也就是说只要手动移动鼠标然后输入需要进行的改变,然而,也可以用非交互式的方式进行文本编辑。例如,可以只用一个简单的命令一次性更改多个文件。

 

  1. tr——替换或删除字符

      tr是替换字符命令,可以将其看做一种基于字符的查找和替换/删除操作。所谓替换,实际是指将字符从一个字母更换为其他字母。例如,将小写字母转变为大写字母。如下图便是一个利用tr进行大小写字母替换的例子。

      该输出结果表明,tr可对标准输入进行操作并且将结果以标准形式输出。tr有两个参数:等待转换的字符集和与之想对应的替换字符集。字符集的表示方法可以是下面三种方式的任一种。

  • 枚举列表: 例如:ABCDEFGHIJKLMNOPQRSTUVWXYZ
  • 字符范围: 例如A-Z。请注意,这种方法有时会与其他命令一样受限于同一个问题(由于不同系统的排序顺序),因此使用时要小心。
  • POSIX字符类;例如[:upper:]。

      多数情况下,这两个字符集应该是同等长度;然而,第一个字符集比第二个字符集长也是可能的,例如下面的例子:

      shell> echo "lowercase letter" | tr [:lower:] A

      除了替换,tr还可以直接从输入流中删除字符。本章前篇,讨论了将MS-DOS类型的文本文件向UNIX类型转换的问题。要进行这样的转换,需要移除每行末尾的回车符,如此便可以通过tr命令解决,如下图所示:

      tr除了替换和删除之外,还有一个奇妙的用法。使用-s选项,tr可以“挤兑”(删除)重复出现的字符,如下图所示:

 

      2. sed——用于文本过滤和转换的流编辑器

      sed是stream editor(流式编辑器)的缩写,它可以对文本流、指定文件集或标准输入进行文本编辑。

      sed的用法,总的说来,首先给定sed某个简单的编辑命令(在文本行中)或是包含多个命令的脚本文件名,然后sed便对文本流的内容执行给定的编辑命令。下面是一个非常简单的sed应用实例。

      shell> echo "front" | sed 's/front/back/'  => 输出结果为"back"

      该例先利用echo生成了只包含一个单词的文本流,然后将该文本流交给sed处理,而sed则对文本流执行“s/front/back/”指令,最后输出“back”作为运行结果。所以可以认为,本例中的sed与vi中的替代(查找和替换)命令相似。 

      sed中的命令总是以单个字母开头。上例中,替换命令便由字母s代替,其后紧跟替换字符,替换字符由作为分界符的斜线字符分开。分界符的选择是随意的,习惯上一般使用斜线,但是sed支持任意字符作为分界符,sed会默认紧跟在sed的命令之后的字符为分界符。下面的命令可以起到相同的效果。

      shell> echo "front" | sed "s_front_back_"  => 输出结果为“back”

      由于下划线是紧跟在命令字符“s”之后的,所以它便是分界符。由此可见,这种自动设定分界符的能力增强了命令行的只读性。

      sed中的多数命令允许在其前添加一个地址,该地址用来指定输入流的哪一行被编辑。如果该地址省略了,便会默认对输入流的每一行执行该编辑命令。最简单的地址就是一个行号。例如,在上例中增加一个“1”,如下所示:

      shell> echo "front" | sed "1s/front/back/"    => 输出结果为“back”

      增加的“1”表示此替换操作只对输入流的第一行起作用。当然也可以指定其他行号,如下所示:

      shell> echo "front" | sed "2s/front/back/"  

      结果显示此替换操作并未执行,这是因为该输入流中并没有第二行。但是可以采用如下的方式来操作, 具体见下图:

      sed替换命令的地址表示法有多种,下表列出了经常使用的几个:

地址 功能说明
N n是正整数表示行号
addr1, addr2 行范围,表示从addr1至addr2的所有行。
/regexp/ 用POSIX基本正则表达式描述的行。请注意,这里的正则表达式是用斜线作为分隔符,当然,也可以自己选择分界符,只要用\cregexpc选项即可,这里的c就是用于取代斜杠的分界符

      接下来,我们会用本章前面所使用的distros.txt文件演示多种不同形式的地址表达。首先,用行号范围的表达方式如下图所示:

      此例显示了distros.txt文件中的第1行到第5行的内容,利用了p命令输出指定匹配行的内容,从而完成上述的操作。然而,要想得到正确结果,就必须添加选项-n(不会自动打印选项)以防sed会默认输出每一行的内容。

      下面尝试使用正则表达式:

      此处用的是斜杠隔开的正则表达式/SUSE/,查找包含SUSE字符串的文本行,该用法与grep的用法类似。

      最后,尝试在地址前添加表示否定意义的感叹号(!),用法如下图所示,这里特别需要注意得是使用单引号,而不是双引号,双引号会报错。

      这样我们得到了理想输出结果,即除了那些与正则表达式匹配的行,其他所有的行都显示出来了。

      到目前为止,我们已经介绍了sed的两个编辑命令——s和p,下表是一张更加完整的基本编辑命令指令表。

命令 功能描述
= 输出当前行号。例如:sed '=' file
a 在当前行后附加文本。例如,sed 'a\jpx' file
d 删除当前行
i 在当前行插入文本。例如,sed 'i\jpx' file
p 打印当前行。默认情况下,sed会输出每一行并且只编辑文件内那些匹配指定地址的行。当指定-n选项时,默认操作会被覆盖
q 退出sed不再处理其他行。如果没有指定-n选项,就会输出当前行。
Q 直接退出sed不再处理行
s/regexp/replacement 将regexp的内容替换为replacement代表的内容。
y/set1/set2 将字符集set1转换为字符集set2.请注意,与tr不同的是,sed要求这2个字符集等长。

      s命令是目前为止使用最普遍的编辑命令。接下来,我们通过编辑distros.txt文件来演示其强大功能的一小部分。之前我们已经讨论过distros.txt文件中的时间字段并不是以“计算机友好”的形式存储,因为此时间形式是MM/DD/YYYY,YYYY-MM-DD这样的形式会方便很多(更容易排序)。但如果手动更改文件,不仅浪费时间而且容易出错,而sed可以一步完成这样的操作。首先先看一下命令的执行结果图:

      这个命令看起来还真复杂,但它确实有效。只需一步,就改变了文件中的日期形式。下面我们来分析一下它的各个组成部分的含义。首先,sed的s命令的结果如下:

      shell> sed 's/regexp/replacement/' distros.txt

      接下来我们需要理解将日期分隔开来的正则表达式。由于它是以MM/DD/YYYY的形式存在,并且出现在行末尾,所以采用如下的表达式:

      [0-9]{2}/[0-9]{2}/[0-9]{4}$

      该表达式的匹配格式:两位数字、斜杠、两位数字、斜杠、4位数字以及行尾标志。所以这代表了regexp表达式的形式,但是怎么处理replacement的表达式?解决这一问题,我们必须引进正则表达式的一个新特性,该特性一般存在于那些使用BRE的应用中。此特性称为回参考,并且工作方式类似于此,即如果replacement中出现了\n转义符,并且这里的n是1-9之间的任意数字,那么转义字符就是指前面正则表达式中与之对应的子表达式。如何创建该表达式,可以简单地将其扩在括号中,如下所示:

      ([0-9]{2})/([0-9]{2})/([0-9]{4})$

      现在,我们便有了3个子表达式。第一个的内容是月份,第二个内容是具体的日,第三个内容则是指年份。于是便用如下命令行构建替换字符。\3-\1-\2。该表达式表示的顺序如下:年份、斜杠、月份、斜杠和具体的日。于是整个命令行就变成了下面的形式:

      shell> sed 's/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2' distros.txt

      但是仍有两个遗留问题:第一,当sed试图编译s命令时,正则表达式中多余的斜杠会令sed混淆;第二,sed默认情况下只接受基本正则表达式,所以正则表达式中的部分元字符会被当成文字字符。我们可以利用反斜杠来避免这些冒犯字符,从而一次性解决这2个问题。

      shell> sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/' distros.txt

      这样就大功告成了。

      s命令的另外一个特点,就是替换字符串后面可以紧跟可选择标识符。其中最重要的标识符是g,该标识符告诉sed对每行的所有匹配项进行替换操作,而不是默认的只替换第一个匹配项。示例如下:

      shell> echo "aaabbbccc" | sed 's/b/B/'  =>  返回结果是aaaBbbccc

      我们可以看到执行了替换操作,但只是对第一个字母b有效,剩下的字母并没有改变。通过增加g标识符,便可以对所有的b进行替换操作。

 

      3. aspell——交互式拼写检查工具

      最后一个所要讨论的文本工具就是aspell,它是交互式的拼写检查工具。虽然aspell命令通常为那些需要进行拼写检查的程序所用,但它同样可以作为一个独立于命令行的工具发挥其效用。它可以智能地检查不同类型文本文件的错误,包括HTML文件、C/C++程序、email消息以及其他专业的文本文件。

      检查一篇简单散文的拼写错误,可以用如下方式使用aspell。

      shell> aspell check textfile

      此处的testfile是要进行检查的文件名。作为实例进行详解,下面创建了一个简单的foo.txt文本文件,它包含一些故意的拼写错误。

      shell> cat > foo.txt

      The quick brown fox jimped over the laxy dog.

      接下来使用aspell检查文件中的拼写错误。由于aspell在检验模式下是与用户交互的,所以我们可以看到如下的显示界面。

      显示内容的顶部,被怀疑错误的字符是以高亮的形式显示的。中间部分,有10个标号从0~9的替换拼写建议以及其他可能的动作选项。最后,末端有一个提示框供用户进行操作选择。

      除非额外指定了命令行选项--dont-backup,不让aspell将会创建一个包含原文本内容的备份文件,此备份文件文件名则由原文件名加上后缀.bak组成。

      接下来就来讨论一下aspell如何处理不同类型的文件。使用诸如vim的文本编辑器,给文件增加一些HTML。如果试图使用命令aspell check命令的话,aspell会认为HTML标签的所有内容是拼写错误。该问题可以通过增加-H模式选项来克服。当使用了-H选项选项后,HTML语言部分就被忽略了,只有那些非HTML标签部分才会被检查。在这种模式下,HTML标签内容被忽略了并且不会进行拼写检查。

 

posted @ 2015-03-10 15:08  jplatformx  阅读(381)  评论(0编辑  收藏  举报