sed与awk第二版

第一章

强大的编辑工具

我的妻子不让我买动力锯,她怕我用时会发生意外。所以我依靠手锯进行类似搭架子这样的周末工程。然而,如果我从事木工工作,那么我就必须使用动力锯。动力锯的速度和效率对多产是很重要的。[D.D.]

对于创建和修改文本文件的人来说,sed和awk是编辑的动力工具。这些工作中所做的大部分事情都可以用文本编辑器交互式地完成。然而,使用sed和awk在达到同样结果的情况下可以节省大量重复性工作。

sed和awk是特殊的,它们需要花时间来学习,但是它们的功能将带给你事半功倍的效果,尤其是当文本编辑是你的职业的正常组成部分时。

解决有趣的问题

学习sed和awk的主要动机是因为,在解决文本编辑的一般问题时它们很有用(注1)。有些人(包括我自己)对某个问题是否乐于去解决,取决于该问题是否单调乏味。如果让我选择使用vi还是sed来对大量文件进行一系列重复性编辑工作,我选择sed,因为对我来说,它可以使这些问题更加有趣。我总结了一个解决方案以取代重复一系列按键。除此之外,一旦完成了任务,我会为自己的聪明而自豪。好像有了一点魔法一样,省去了一些枯燥的劳动。

最初,使用 sed 和 awk 来完成一项任务看起来像是需要很长的时间。在尝试几次之后,你可能还会觉得用手工完成任务还更容易些。耐心点!你不但需要学习如何使用 sed 和 awk,而且还要了解在什么情况下使用它们更好。你越精通,解决问题就越快而且也越广。

你还有机会发现解决特殊问题的一般方法。你会发现,考虑某种问题的方式如果与一类问题相关,这样就能够设计出在其他情况下可以重用的解决方案。

我们给出了一个示例(没有给出任何程序代码)。在我们写的书中有一本使用了交叉引用命名模式,即引用由格式化软件(sqtroff)定义和处理。在文本文件中,对有关错误处理一章的引用可能是按如下方式编码的

\*[CHerrorhand]

“CHerrorhand"是引用的名字,”\*[“和”’]是区分引用和其他文本的调用序列。在中心文件中,文档中用于交叉引用的名字被定义为sqtroff字符串。例如,“CHerrorhand"被定义为"Chapter 16,Error Handling”(像这样使用符号交叉引用模式而不使用显式引用的优点是,如果添加、删除或重新排序文章,只需要编辑中心文件就可以反映新的组织结构)。当用格式化软件处理文档时,引用被正确地重新解析和扩展。

我们面临的问题是必须使用同样的文件来创建本书的在线版本。因为不能使用sqtroff格式化软件,所以需要利用一些方式来扩展文件中的交叉引用。换句话说,就是不希望文件中包含"\*[CHerrorhand]",而想要"CHerrorhand"引用的内容。

解决这个问题有3种可能的方式:

  1. 使用文本编辑器查找所有的引用并用适当的文字串代替它们。

  2. 使用 sed 进行编辑工作。这类似于手工编辑工作,只是比较快。

  3. 使用 awk 编写一个程序:(a)用来读取中心文件,并生成引用名及其定义的列表;(b)读取文档查找引用调用序列;(c)查寻列表中引用的名字并用其定义代替它。

第一种方法明显地很耗时(而且没有趣!)。第二种方法(使用 sed)的优点是它可以创建一个工具来完成这项工作。例如, 编写寻找"\*[CHerorrhand]“并用"Chapter 16, Error Handling"来代替它的 sed 脚本相当简单。同样的脚本可用于修改文档的每个文件。缺点是这种替换是硬编码的, 也就是说, 对于每个交叉引用, 都需要编写一条命令来进行替换。第三种方法(使用 awk)构建一个工具, 这个工具对遵循这种语法的任何交叉引用都起作用。这个脚本也可以对于其他书中的交叉引用进行扩展, 它将大大节省你编写特殊替换列表的时间。它是这三种方法中最常用的解决方案, 并且被设计为最可能重用的工具。

解决问题中的部分工作是要知道构建哪种工具。有时 sed 脚本可能是: 一个比较好的选择, 因为问题本身可能无法用 awk 进行处理, 或者可能需要非常冗长复杂的 awk 脚本才能解决。必须要记住 sed 和 awk 对什么样的应用是最适合的。

字符流编辑器

sed 是一个"非交互式的"面向字符流的编辑器。它和许多 UNIX 程序一样, 被认为是面向字符流的, 这是因为输入流通过程序并将输出直接送到标准输出端。(例如, vi 不是面向字符流的, 大多数 DOS 应用程序也不是)。输入一般来自文件, 但是也可以直接来自键盘(注 2)。输出在默认情况下输出到终端屏幕上, 但是也可以输出到文件中。sed 通过解释脚本来工作, 该脚本指定了将要执行的动作。

sed 提供的功能好像是交互式文本编辑的自然延伸。例如, 它提供的查找和替换程序可以被全局地应用于单个文件或一组文件。在处理一个特定文件中只出现一次的术语时, 你可能不常用 sed, 但你会发现利用它处理很多文件中的一系列修改是很有用的。只要想到在大约 100 多个文件中, 处理 20 个不同的编辑工作可以在几分钟之内完成, 你就会知道 sed 的强大了。

使用 sed 和编写简单的 shell 脚本(或 DOS 中的批处理文件)类似, 可以指定一系列顺序执行的动作。其中的大多数动作可以在 vi 的内部用手工来完成: 替换文本、删除行、插入新文本等等。sed 的优点是可以在一个地方指定所有的编辑指令, 然后通过文件传递一次来执行它们。你不必进入每个文件来做每次修改。sed还可以有效地用于编辑非常庞大的文件,而这个文件利用交互式编辑进行处理将会很慢。

在创建和维护文档的过程中也经常会用到sed,尤其当文档由独立的章节组成,而且每个章节又存在不同的文件中时。通常,在评审返回文档的草稿之后,有许多修改可以应用于所有的文件。例如,在软件文档设计过程中,软件的名字或它的组件可能会改变,你必须找到它们并进行修改。使用sed进行修改将是一个非常简单的过程。

sed能够用来保持整个文档的一致性。你可以查找到某个特定术语的所有不同的使用方式并且使它们变得一致。在通过troff进行格式化之前,可以使用sed插入特殊的排版代码或符号。例如,利用sed可以用ASCII字符代码取代前后的双引号(“弯引号"而不是"直引号”)。

sed还可以作为编辑过滤器使用。换句话说,你可以处理输入文件并将输出结果发送到另一个程序。例如,你可以使用sed分析纯文本文件并在将输出送到troff进行格式化之前插入troff宏。它允许你动态(on the fly)编辑(也许是临时的)。

作者或出版商可以用sed编写大量的转换程序,例如,将Scribe或EX文件中的格式化代码转换成troff,或者转换PC字处理文件,例如WordStar。稍后,我们将看到一个用在Ventura Publisher中的将troff宏转换成样式表标签的sed脚本(也许利用sed可以将用某种语言的语法编写的程序,转换成具有另一种语言的语法的程序)。当Sun公司首次产生Xview时,它们发布了一些将SunView程序转换成Xview的转换程序,这些程序主要由sed脚本组成,可以转换不同函数的名字。

sed拥有用于构建更复杂的脚本的几个基本的程序设计结构。它在每次多于一行的处理能力方面有限制。

除了最简单的sed脚本外,几乎所有的都是从"shell实现"中调用的,shell实现是一种shell脚本,它调用sed而且还包含sed执行的命令。shell实现是一个命名和执行单字命令的简单方式。使用这个命令的用户甚至不需要知道sed正在被使用。一个shell包装器的例子如phrase脚本(在本书后面我们将看到这个脚本)。它允许你匹配包含两行的模式,寻址grep的特殊限制。

使用 sed 的小结:

  1. 在一个或多个文件上自动实现编辑操作。

  2. 简化对多个文件执行相同的编辑处理工作。

  3. 编写转换程序。

模式匹配的程序设计语言

将awk称为程序设计语言会吓跑一些人。如果你是其中之一,可以把awk看做是解决问题的一种不同方法,即利用这种方法可以对计算机所要做的事情增加更多的控制。

sed 很容易被看成是与交互式编辑相反的程序。sed 程序与如何手动地应用编辑命令是很相近的。sed 限制你利用在文本编辑器中使用的方法。awk 为处理文件提供了更一般的计算模型。

awk 程序的典型示例是将数据转换成格式化的报表。这些数据可能是由 UNIX 程序(例如 uucp)产生的日志文件,而且报表以一种对系统管理员有用的格式将数据汇总。另,个例子是由独立的数据项和数据检索程序组成的数据处理应用程序。数据项是以结构化方式记录数据的过程。数据检索是从文件中提取数据并生成报告的过程。

所有这些操作的关键是数据拥有某种结构。我们可以利用衣柜来类比解释它。衣柜由多个抽屉组成,每个抽屉都放入一些特定的内容:一个抽屉里是短袜,另一个抽屉是内衣,第三个抽屉是运动衫。有时抽屉里可以分隔成几个隔间并将不同种类的东西存放在一起。这些就是决定了东西放在哪里的结构,当你挑选要送去洗的东西以及当你要穿衣服时,就可以在它们所在的地方找到。awk 允许你在编写放进和拿出东西的程序中使用文本文件结构。

因此,当数据拥有某种结构时就能最好地体现awk的好处。文本文件的结构或松或紧。包含主要的节和次要的节的一章也是一种结构。我们将看到一个脚本,用于提取章节标题并将它们编号以生成一个大纲。由制表符分隔的列项目所组成的表是高度结构化的。可以使用awk脚本对数据的列重新排序, 甚至可以将列变成行以及将行变成列。

和sed脚本一样,awk脚本一般是利用shell包装器来调用。shell包装器是一个shell脚本,它通常包含调用awk的命令行以及awk解释的脚本。简单的一行awk脚本可以从命令行输入。

下面是awk能够完成的一些功能:

将文本文件看做由记录和字段组成的文本数据库。

使用变量操作数据库。

使用算术和字符串操作符。

使用普通的程序设计结构,例如循环和条件。

生成格式化报告。

定义函数。

从脚本中执行UNIX命令。

处理UNIX命令的结果。

更加巧妙地处理命令行的参数。

更容易地处理多个输入流。

由于这些特征, 用户可以根据awk所具有的能力和适用范围来处理由shell脚本执行的各种任务。在本书中, 你将看到一个基于菜单的命令生成器, 一个交互式拼写检查器和一个索引处理程序的示例, 所有这些示例都使用了上面提到的功能。

awk的功能将文本编辑的思想扩展到计算, 使它有可能执行各种数据处理任务, 包括分析、提取和数据报告。实际上, 这是awk的最普通的用法, 但是也有许多不常使用的应用: awk曾被用来编写Lisp解释程序, 甚至是编译程序!

掌握 sed 和 awk 的四个障碍

有许多介绍 UNIX 的书都可以让你了解 sed 和 awk。本书的目的是帮助你以最快和最 容易的方式更进一步掌握 sed 和 awk。

在掌握 sed 和 awk 的过程中存在四个障碍。你必须学会:

  1. 如何使用 sed 和 awk。清除这个障碍相对容易, 因为 sed 和 awk 的工作方式类似, 都基于行编辑器 ed。第二章 “了解基本操作” 概述了使用 sed 和 awk 的机制。

  2. 应用 UNIX 正则表达式语法。使用 UNIX 正则表达式语法进行模式匹配是 sed 和 awk 共有的特征, 对许多其他的 UNIX 程序也一样。这是一个较困难的障碍, 原因有两个: 语法是神秘的, 虽然许多人都有使用正则表达式的经验, 但是很少有人能完全掌握语法。对语法的使用越熟悉, 那么对 sed 和 awk 的使用也越容易。这就是我们为什么要花大量时间在第三章 “了解正则表达式语法” 中讨论正则表达式的原因。

  3. 如何与 shell 进行交互。当与 sed 和 awk 本身不直接相关时, 管理与 shell 命令的交互是一个困难的问题, 因为 shell 与两个程序共享大量特殊的字符。如果可以的话, 通过在独立的文件中放置脚本来避免这种问题。如果不行的话, 就为脚本使用兼容的 Bourne shell (引用规则更加直观), 并使用单引号包含你的脚本。如果将 csh 作为你的交互式 shell, 记住要用反斜线 (”!" 转义感叹号。没有其他的方式能让 csh 别管感叹号 (注 3)。

  4. 脚本编写的技巧。这是最困难的, 这就像跨栏竞赛中的高栏。因此, 本书大部分都介绍脚本编写。使用 sed, 你必须了解一组单字母的命令。使用 awk, 你必须了解程序设计语言的语句。要掌握脚本编写的技巧, 不仅必须注意大量的示例, 而且必须亲自尝试编写脚本。

如果你正进行跨栏竞赛, 只是跃过跨栏并不能赢得竞赛, 而需要的是快速跃过它们才能赢得比赛。在编写脚本中, 学习脚本化命令集或语言只是简单地"跳过了跨栏", 要让你的脚本处理问题更有趣, 就要获得在竞赛中跑得更快的能力。