sed & awk 概述

摘自《sed与awk》 Dale Dougberty&Arnold Robbins 著

如果你正要开始学习 sed 与 awk,最好从了解它们的共同点入手:

  • 它们都使用相似的语法来调用。
  • 它们都是面向字符流的,都是从文本文件中一次一行的读取输入,并将输出直接送到标准输出端。
  • 它们都使用正则表达式进行模式匹配。
  • 它们允许用户在脚本中指定指令。

它们有如此多的共同点,原因之一是它们都起源于相同的行编辑器—— ed。下面首先对 ed 做简短介绍,再介绍 sed 和 awk 是如何一步步形成可编程的编辑器的。sed 和 awk 的区别在于它们控制所做的工作时所用的指令不同。这是一个主要的区别,而且这影响了这些程序最适于处理的任务类型。

awk 起源于 sed 和 grep 而不是 ed

可以将 awk 的起源追溯到 sed 和 grep,并且经由这两个程序追溯到 ed(最初的UNIX行编辑器)。

如果使用过行编辑器,那么理解 sed 和 awk 的行定位就会更容易。如果使用过 vi(全屏幕的编辑器),那么你一定熟悉由底层的行编辑器 ex(它依次是 ed 中的特征的扩展集)衍生的大量命令。

下面来看一些使用行编辑器 ed 的基本操作。不要担心,这只是帮助你了解 sed 和 awk 的练习,而不是想让你相信行编辑器的奇妙。这个练习中出现的 ed 命令和稍后要学的 sed 命令相同。你可以自由地使用 ed 做实验,以便对它如何工作有一个了解。

使用行编辑器,每次可以处理一行。知道处于文件中的哪一行是很重要的。当使用 ed 打开文件时,它显示了文件中的字符个数并定位在最后一行

1
2
$ ed test
339

没有提示符。如果输入了 ed 不理解的命令,它将打印一个问号作为错误消息。可以输入打印命令 p 来显示当前的行。

1
2
p
label on the first box.

默认情况下,一个命令只影响当前的行。要进行一项编辑工作,首先要移至想要编辑的行,然后应用相应的命令。要移到某一行,就要指定它的地址(address)。一个地址可以由一个行号、一个指示文件中特定位置的符号或一个正则表达式组成。通过输入行号 1 可以转到第一行,然后输入删除命令来删除那一行。

1
2
3
1
You might think of a regular expression
d

输入“1”使第一行成为当前行,并在屏幕上显示它。ed 中的删除命令是d,上例中是删除当前行。与移至某行然后再对它进行编辑不同的是,可以将标识命令对象的某一行或某些行的地址,放在编辑命令的前面作为命令的前缀。例如,如果输入“1d”,那么第一行就被删除。

还可以将一个正则表达式作为一个地址。为了删除包含单词“regular”的行,可以使用下面的命令:

1
/regular/d

其中的斜杠界定的对象是正则表达式,“regular”是想要匹配的字符串。这个命令删除包含“regular”的第一行并且使跟在它后面的这一行成为当前行。
注:确信(be sure)你已经理解了使用删除命令来删除整个行。它不只是删除那一行上的单词“regular”。
要删除包含这个正则表达式的所有行,可以在命令前面加上字母g,表示该命令是一个全局命令。

1
g/regular/d

全局命令使匹配正则表达式的所有行成为特定命令的对象。

迄今为止只是使用了删除文本的命令,替代文本(用文中的一部分取代另一部分)更为有趣。ed 中的替换命令 s 是:

1
[address]s/pattern/replacement/flag

pattern 是一个正则表达式,并用 replacement 替代当前行中与这个正则表达式匹配的字符串。例如,下面的命令用“complex”取代当前行上第一次出现的“regular”。

1
s/regular/complex/

由于没有指定地址,所以它只影响当前行上的第一次出现。如果当前行上没有找到“regular”则出现一个错误。为了寻找同一行上的多次出现,必须指定 g 作为标志:

1
s/regular/complex/g

这个命令改变了当前行上的所有的出现。必须指定地址从而使该命令不只是对当前行操作。下面的替换命令指定了一个地址:

1
/regular/s/regular/complex/g

这个命令影响文件中与这个地址匹配的第一行。记住,第一个“regular”是一个地址,第二个是匹配替换命令的模式。要将它应用于所有的行,必须使用全局命令,即在地址前放置g

1
g/regular/s/regular/complex/g

现在,这个替换应用于所有的地方,即所有行上的所有出现。
注:注意“g”的不同含义。开始处的“g”是全局命令,意味着对所有与地址匹配的行进行改变。结尾处的“g”是一个标志,意味着改变一行上的每个出现,不只是第一个。

地址和模式不必相同。例如:

1
g/regular expression/s/regular/complex/g

表示在包含字符串“regular expression”的任意行上,用“complex”代替“regular”。
如果地址和模式相同,那么可以通过指定两个连续的定界符(//)来告诉 ed。

1
g/regular/s//complex/g

在这个例子中,“regular”被指定为“地址”,同时应用相应的地址匹配替换模式。

PS:到此为止,也不难理解 vi 中的“搜索及替换”命令了。

vi 编辑器中的“搜索及替换”命令:

命令 说明
/pattern 从光标开始处向文件尾搜索pattern
?pattern 从光标开始处向文件首搜索pattern
n 在同一方向重复上一次搜索命令
N 在反方向上重复上一次搜索命令
:s/p1/p2/g 将当前行中所有p1均用p2替换
(如果去掉结尾处的“g”标志,则只是替换当前行中p1的第一次出现)
:n1,n2 s/p1/p2/g 将第n1至n2行中所有p1均用p2替换
(如果去掉结尾处的“g”标志,则只是替换匹配的每一行中p1的第一次出现)
:g/p1/s//p2/g 将文件中所有p1均用p2替换

类似的 UNIX 实用工具 grep 来源于 ed 中的下面的全局命令:

1
g/re/p

(PS:估计这也是程序名称“grep”的由来吧)

它表示“全局正则表达式打印”。grep 是从 ed 中提取并可用做外部程序的行编辑命令。它是执行一个编辑命令的“硬连接(hard-wired)”。将正则表达式作为命令行上的一个参数并将它用做要打印的行的地址。如下例所示,寻找匹配“box”的行:

1
2
3
$ grep 'box' test
You are given a series of boxes,the first one labeled "A",
label on the first box.

它打印匹配正则表达式的所有的行。

注:在使用正则表达式作为命令行上的一个参数时,假如模式中包含有可以由 shell 解释的空格或任意字符(例如$*),那么必须用单引号括住。这也是在命令行上使用正则表达式时的一个良好习惯——正则表达式要正确地传递到使用它的程序而不只是由 shell 解释。

ed 的一个更有趣的特征是脚本化编辑工作的能力,将编辑命令放在独立的文件中并将它们作为行编辑器的输入。例如,如果将一系列命令放到名为 ed-script 的文件中,下面的命令将执行这个脚本:

1
ed test < ed-script

这个特征使 ed 成为可编程的编辑器。也就是说,你可以脚本化任何手动执行的操作。

ed、sed、awk

sed 是作为特殊目的的编辑器而创建的,用于专门执行脚本;与 ed 不同,它不能交互地使用。sed 与 ed 的主要区别在于它是面向字符流的。默认情况下,到 sed 的所有输入都会经过相应的处理,并转为标准输出。输入文件本身不发生改变。如果确实想改变输入文件,一般使用 shell 机制进行输出重定向(注:不要将来自命令的输出重定向到输入文件,否则会改写输入文件。甚至可能在 sed 处理这个文件之前发生,并破坏你的数据。),当你对所做的编辑工作满意时,用修改后的版本代替最初的文件。

ed 不是面向字符流的,并且文件本身会发生改变。ed 脚本必须包含保存文件并退出编辑器的命令。它不产生到达屏幕的输出,但由特殊命令生成的东西除外。

sed 的字符流定位对如何应用寻址有重要的影响。在 ed 中没有指定地址的命令只影响当前行。sed 遍历文件,每次一行,这样每一行都成为当前行,而且每一行都应用这个命令。结果是 sed 对文件中的每一行应用了没有地址的命令。

看一下下面的替换命令:

1
s/regular/complex/

如果在 ed 中交互式的输入这个命令,则用“complex”取代当前行上第一次出现的“regular”。在 ed 脚本中,如果这是脚本中的第一个命令,那么它就只是应用于文件的最后一行(ed 的默认当前行)。然而,在 sed 脚本中,相同的命令应用于所有的行。也就是说,sed 命令是隐式的全局命令。在 sed 中,上一个示例的命令和 ed 中如下所示的全局命令结果相同。

1
g/regular/s//complex/

注:理解 ed 中的当前行寻址与 sed 中全局行寻址之间的区别是很重要的。在 ed 中,使用寻址扩大受命令影响的行数;在 sed 中,使用寻址限制受命令影响的行数。

awk 是作为可编程的编辑器而开发的,同 sed 一样,它也是面向字符流的,并且解释编辑命令的脚本。awk 与 sed 不同的地方是它废弃了行编辑器的命令集。它提供了仿效 C 语言的程序设计语言,例如,print 语句取代 p 命令;但延续了寻址的概念,例如:

1
/regular/ {print}

用于打印匹配“regular”的那些行。大括号({})用于包围应用于同一个地址的一个或多个语句。

在脚本中使用程序设计语言的优点是,它提供了更多的方式来控制可编程的编辑器所做的事情。awk 提供了表达式、条件语句、循环和其他程序设计结构。

awk 最独特的特征之一是它分析或拆分每个输入行,并生成可用于脚本处理的独立的单词(一个编辑器,例如 vi,也识别单词,允许一个单词一个单词的移动,或者使一个单词成为操作对象,但是这些特征只能在交互式下使用)。虽然 awk 是作为可编程的编辑器设计的,但它还可以完成许多其他任务。

更多的关于 sed & awk ,请详读《sed & awk》 Dale Dougberty & Arnold Robbins 著