sed与awk-第二章-了解基本操作
第二章了解基本操作
如果你正要开始学习sed和awk,最好从了解它们的共同点入手:
- 它们都使用相似的语法来调用。
- 它们都是面向字符流的,都是从文本文件中,一次一行地读取输入,并将输出直接送到标准输出端。
- 它们都使用正则表达式进行模式匹配。
- 它们允许用户在脚本中指定指令。
它们有如此多的共同点,原因之一是它们都起源于相同的行编辑器——ed。在这一章中,我们首先对ed做简短介绍,再介绍sed和awk是如何一步步形成可编程的编辑器的。
sed和awk的区别在于它们控制所做的工作时所用的指令不同。这是一个主要的区别,而且这影响了这些程序最适于处理的任务类型。
本章将介绍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,可以直接跳到下一节)。
使用行编辑器,每次可以处理一行。知道处于文件中的哪一行是很重要的。当使用ed打开文件时,它显示了文件中的字符个数并定位在最后一行。
没有提示符。如果输入了ed不理解的命令,它将打印一个问号作为错误消息。可以输入打印命令p来显示当前的行。
P
label on the first box.
默认情况下,一个命令只影响当前的行。要进行一项编辑工作,首先要移至想要编辑的行,然后应用相应的命令。要移到某一行,就要指定它的地址(address)。一个地址可以由一个行号、一个指示文件中特定位置的符号或一个正则表达式组成。通过输入行号1可以转到第一行,然后输入删除命令来删除那一行。
1
You might think of a regular expression
d
输入"1"使第一行成为当前行,并在屏幕上显示它。ed中的删除命令是d,上例中是删除当前行。与移至某行然后再对它进行编辑不同的是,可以将标识命令对象的某一行或某些行的地址, 放在编辑命令的前面作为编辑命令的前缀。例如, 如果输入 “ld”, 那么第一行就被删除。
还可以将一个正则表达式作为一个地址。为了删除包含单词 “regular” 的行, 可以使用下面的命令:
/regular/d
其中的斜杠界定的对象是正则表达式, “regular” 是想要匹配的字符串。这个命令删除包含 “regular” 的第一行并且使跟在它后面的这一行成为当前行。
注意: 确信你已经理解了使用删除命令来删除整个行。它不只是删除那一行上的单词 “regular”。
要删除包含这个正则表达式的所有行, 可以在命令前面加上字母 g, 表示该命令是一个全局命令。
g/regular/d
全局命令使匹配正则表达式的所有行成为特定命令的对象。
迄今为止你只接触到了删除文本。替代文本 (用文本中的一部分取代另一部分) 更有趣。ed 中的替换命令 s 是:
[address]s/pattern/replacement/flag
pattern 是一个正则表达式, 并用 replacement 替代当前行中与这个正则表达式匹配的字符串。例如, 下面的命令用 “complex” 取代当前行上第一次出现的 “regular”。
s/regular/complex/
由于没有指定地址, 所以它只影响当前行上的第一次出现。如果在当前行上没有找到 “regular” 则出现一个错误。为了寻找同一行上的多次出现, 必须指定 g 作为标志:
s/regular/complex/g
这个命令改变了当前行上的所有的出现。必须指定地址从而使该命令不只是对当前行操作。下面的替换命令指定了一个地址:
/regular/s/regular/complex/g
这个命令影响文件中与这个地址匹配的第一行。记住,第一个"regular"是一个地址,第二个是匹配替换命令的模式。要将它应用于所有的行,必须使用全局命令,即在地址前放置g:
g/regular/regular/complex/g
现在,这个替换应用于所有的地方,即所有行上的所有出现。
注意:注意"g"的不同含义。开始处的"g"是ed命令全局模式,意味着对所有与地址匹配的行进行改变。结尾处的"g"是一个标志,意味着改变一行上的每个出现,不只是第一个,结尾的
g是s(substitute 替换)命令的标志位 (flag),结尾的g只属于s不属于ed全局命令。
地址和模式不必相同。例如:
g/regular expression/s/regular/complex/g
表示在包含字符串"regular expression"的任意行上,用"complex"代替"regular"。如果地址和模式相同,那么可以通过指定两个连续的定界符(//)来告诉ed。
g/regular/s//complex/g
在这个例子中,“regular"被指定为"地址”,同时应用相应的地址匹配替换模式。这些命令中有大量的内容需要掌握,然而看上去我们对这些命令的介绍却只是一带而过,不要担心,稍后还要讲到这些命令。
类似的UNIX实用工具grep来源于ed中的下面的全局命令:
g/re/p
它表示"全局正则表达式打印"。grep是从ed中提取并可用做外部程序的行编辑命令。它是执行一个编辑命令的"硬连接(hard- wired)"。将正则表达式作为命令行上的一个参数并将它用做要打印的行的地址。如下例所示,寻找匹配"box"的行:
$ grep ‘box’ test
You are given a series of boxes, the first one labeled “A”,
label on the first box.
它打印匹配正则表达式的所有的行。
ed 的一个更有趣的特征是脚本化编辑工作的能力, 将编辑命令放在独立的文件中并将它们作为行编辑器的输入。例如, 如果将一系列命令放到名为 ed- script 的文件中, 下面的命令将执行这个脚本:
ed test < ed- script
这个特征使 ed 成为可编程的编辑器。也就是说, 你可以脚本化任何手动执行的操作。
sed 是作为特殊目的的编辑器而创建的, 用于专门执行脚本; 与 ed 不同, 它不能交互地使用。sed 与 ed 的主要区别在于它是面向字符流的。默认情况下, 到 sed 的所有输入都会经过相应的处理, 并转为标准输出。输入文件本身不发生改变。如果确实想改变输入文件, 一般使用 shell 机制进行输出重定向, 当你对所做的编辑工作满意时, 用修改后的版本代替最初的文件。
ed 不是面向字符流的, 并且文件本身会发生改变。ed 脚本必须包含保存文件并退出编辑器的命令。它不产生到达屏幕的输出, 但由特殊命令生成的东西除外。
sed 的字符流定位对如何应用寻址有重要影响。在 ed 中没有指定地址的命令只影响当前行。sed 遍历文件, 每次一行, 这样每一行都成为当前行, 而且每一行都应用这个命令。结果是 sed 对文件中的每一行应用了没有地址的命令。
看一下下面的替换命令:
s/regular/complex/
如果在 ed 中交互式地输入这个命令, 则用 “complex” 取代当前行上第一次出现的 “regular”。在 ed 脚本中, 如果这是脚本中的第一个命令, 那么它就只应用于文件的最后一行 (ed 的默认当前行)。然而, 在 sed 脚本中, 相同的命令应用于所有的行。也就是说, sed 命令是隐式的全局命令。在 sed 中, 上一个示例的命令和 ed 中如下所示的全局命令结果相同。
g/regular/s//complex/
注意: 理解 ed 中的当前行寻址与 sed 中全局行寻址之间的区别是很重要的。在 ed 中, 使用寻址扩大受命令影响的行数; 在 sed 中,使用寻址限制受命令影响的行数。
sed 还具有一些支持编写脚本的额外命令。在第六章“高级 sed 命令”中可以看到一部分这样的命令。
awk 是作为可编程的编辑器而开发的, 同 sed 一样, 它也是面向字符流的, 并且解释编辑命令的脚本。awk 与 sed 不同的地方是它废弃了行编辑器的命令集。它提供了仿效 C 语言的程序设计语言, 例如, print 语句取代 p 命令; 但延续了寻址的概念, 例如:
/regular/ {print}
用于打印匹配“regular”的那些行。大括号({})用于包围应用于同一地址的一个或多个语句。
在脚本中使用程序设计语言的优点是, 它提供了更多的方式来控制可编程的编辑器所做的事情。awk 提供了表达式、条件语句、循环和其他程序设计结构。
awk 最独特的特征之一是它分析或拆分每个输入行, 并生成可用于脚本处理的独立的单词(一个编辑器, 例如 vi, 也识别单词, 允许一个单词一个单词地移动, 或者使一个单词成为操作对象, 但是这些特征只能在交互方式下使用)。
虽然 awk 是作为可编程的编辑器设计的, 但是用户会发现, awk 脚本也能完成许多其他任务。awk 的作者永远不会想到它被用于编写庞大的程序。但是, 当认识到 awk 能这样使用以后, 作者们修订了这种语言, 创建了 nawk, 为编写庞大的程序和解决多方面的程序设计问题提供了更多的支持。这个新的版本(有较小的改进)目前已经由 POSIX 组织作为标准。
命令行的语法
可以用大致相同的方式调用 sed 和 awk。命令行语法是:
command [options] script filename
几乎和所有的 UNIX 程序一样, sed 和 awk 都可以从标准输入中取得输入并将输出发送到标准输出。如果指定文件名 filename, 输入就取自那个文件。输出包含处理后的信息。标准输出是指屏幕, 而且一般来自这些程序的输出都输出到那里。输出也可被送到一个文件, 例如 shell 中的 I/O 重定向, 但是不允许送到向程序提供输入的同一个文件。
每个命令的 options 是不同的。我们将在以后讨论这些选项 (sed 命令行选项的完整列表可以在附录一 “sed 的快速参考” 中找到, awk 命令选项的完整列表可以在附录二 “awk 的快速参考” 中找到)。
script 指定了要执行的指令。如果在命令行上指定 script, 假如它包含有可以由 shell 解释的空格或任意字符 (例如 \(S\) 和 *), 那么它必须用单引号括起。
sed 和 awk 一个共同的选项是 - f 选项, 这个选项允许你指定脚本文件的名字。随着脚本大小的增长, 将它放置在文件中比较方便。因此, 可以按如下方式调用 sed:
sed - f scriptfile inputfile
图 2- 1 展示了 sed 和 awk 的基本操作。每个程序每次从输入文件中读取一个输入行, 生成该输入行的备份, 并且对该备份执行脚本中指定的指令。因此, 对输入行所做的改动不会影响真正的输入文件。
flowchart TD
subgraph 处理单元
A(输入)
B[输入行]
C(输出)
A --> B
B --> C
end
D[脚本] -.-> B
脚本化
脚本就是告诉程序做什么的地方。至少要包含一行指令。短的脚本可以在命令行上指定,长的脚本通常放在容易被修订和测试的文件中。在编写脚本时,要记住指令执行的顺序以及每个指令如何改变输入行。
在sed和awk中,每个指令都包括两个部分: 模式和过程。模式是由斜杠(/分隔的正则表达式。过程指定一个或多个将被执行的动作。
当读取输入的每行时,程序读取脚本中的第一个指令并检测当前行的模式。如果没有匹配,这个过程被忽略并读取下一个指令。如果有一个匹配,那么执行过程中指定的一个或多个动作。读取所有的指令,而不只是读取与输入行匹配的第一条指令。
当所有可用指令被解释并应用于单个行后,sed输出该行并循环处理每个输入行。另一方面,awk不自动输出行,脚本中的指令控制awk最终所做的事情。
在sed和awk中过程的内容有很大不同。在sed中,过程由类似于行编辑器中使用的那些编辑命令组成。大部分命令由单个字母组成。
在awk中,过程由程序设计语句和函数组成。过程必须用大括号括起。
在下面的一节中,我们将看到几个处理邮件列表的脚本示例。
邮件列表的示例
在接下来的一节中,示例中使用了样本文件,名为list。它包含了名字和地址的列表如下所示:
|
|
如果愿意,可以在你的系统上创建这个文件或者创建一个类似的文件。因为本章中多数示例都很短并且是可交互的,你可以从键盘上输入它们并确认最终结果。
使用 sed
调用 sed 有两种方法:在命令行上指定编辑指令,或者将它们放到一个文件中并提供这个文件的名字。
指定简单的指令
可以在命令行上指定简单的编辑命令。
sed[- e]‘instruction’file
只有在命令行上给出多个指令时才需要用- e选项。它告诉sed将下一个参数解释为指令。当只有一个指令时, sed 可以自己做决定。看一些示例。
使用样本输入文件list,下面的例子使用替换命令s,用"Massachusetts"代替"MA"。
|
|
该指令只影响了3行,但显示出了所有的行。
并不是在任何情况下都需要将指令用单引号包围起来,但是你应该养成这个习惯。使用单引号可以阻止shell解释编辑指令中的特殊字符或空格(shell使用空格决定提交给程序的独立的参数,特殊的shell字符在调用之前被展开)。
例如,以上例子可以不使用单引号,但下一个例子就需要使用单引号,因为替换命令中包含空格:
为了在城市(city)和州(state)之间放置逗号,指令用一个逗号和一个空格取代两字母缩写词前面的空格。
有3种方式可以指定命令行上的多重指令:
- 用分号分隔指令。
|
|
- 在每个指令前放置- e。
|
|
- 使用Bourne shell的分行指令功能(注1)。在输入单引号后按RETURN键,就会出现多行输入的提示符(>)。
|
|
这种技术在C shell中不能使用。C shell中采用的方法是,在每个指令的结尾使用分号,并且通过用反斜杠作为每一行的结束,从而可以输入跨越多行的命令(或者通过输入sh临时进入Bourne shell,然后用以上方法键入命令)。
注1:目前,有许多与Bourne shell兼容的shell,能够像这里描述的那样工作,如ksh、bash、pdksh和zsh等。
在上面的例子中, 有 5 行发生了改变, 当然所有的行都被显示出来。记住在输入文件中不会有什么改变。
失误的命令
sed 命令的语法可以被细化, 在使用时很容易犯错误或者忽略掉需要的元素。注意输入以下不完整的语法将发生的情况:
sed 通常会显示任何它不能执行的行, 但是它不会告诉你使用这个命令所发生的错误是什么 (注 2)。在上述情况下, 标记搜索和替换命令末尾的斜杠丢失了。
以下 GNU sed 给出了更清楚的提示:
脚本文件
在命令行上输入较长的编辑脚本是不实际的。这就是通常最好创建包含编辑指令的脚本文件的原因。编辑脚本只是一系列要依次执行的简单的 sed 命令。这种形式使用 f 选项来指定命令行上的脚本文件的名字, 格式如下:
|
|
将想要执行的所有编辑命令都放置在文件中。我们按照惯例创建临时脚本文件 sedscr。
下面的命令读取 sedscr 中的所有替换命令, 并将这些命令应用于输入文件 list 中的每一行:
|
|
再说一次, 显示在屏幕上的结果是临时的, 输入文件中没有发生改变。
如果 sed 脚本能再次使用, 那么应该重新命名这个脚本并保存它。有用的脚本可以保存在个人的或系统共享库中。
保存输出
只有将 sed 的输出重定向到另一个程序中, 才能够捕获文件中的输出。要完成这项工作需要在一个文件名后面指定一个 shell 的 I/O 重定向符号。例如:
|
|
不要将输出重定向到正在编辑的文件中, 否则就会使它变成乱码 (">" 重定向操作符在 shell 做任何其他事情之前截取文件)。如果想用输出文件取代输入文件, 那么可以采用 mv 命令并将它做为单独的步骤来处理。但是首先要确保编辑的脚本是正确的!
在第四章 “编写 sed 脚本” 中将看到名为 runsed 的 shell 脚本, 它能够自动地创建临时文件并使用 mv 改写原始文件。
阻止输入行的自动显示
sed 的默认操作是输出每个输入行。-n 选项可以阻止自动输出。当指定该选项时, 每个要生成输出的指令都必须包含打印命令 p。请看下面的示例。
将这一输出与本节中的第一个示例进行比较。这里只会显示受命令影响的行。
混合选项(POSIX)
通过合并命令行上的-e和-f选项可以构建一个脚本。该脚本是所有命令按命令的给出顺序组合起来的。在sed的UNIX版本中似乎支持这项功能,但是这种特征在帮助页中没有明确给出。POSIX标准明确地要求这种特征。
选项总结
表2- 1汇总了sed的命令行选项。
表2-1:sed的命令行选项
| 选项 | 描述 |
|---|---|
| -e | 编辑随后的指令 |
| -f | 跟随脚本的文件名 |
| -n | 阻止输入行的自动输出 |
使用awk
与sed相似,awk为每个输入行执行一套指令。可以在命令行上指定指令或创建脚本文件。
运行awk
命令行的语法是:
|
|
每次从一个或多个文件中读入一行或从标准输入中读入一行。指令必须包含在单引号中,从而与shell区别开(指令几乎总是包含大括号和/或美元符号,shell将它们解释为特殊符号)。可以用与sed相同的方式输入多重命令行:用分号分隔命令或使用Borune shell的多行输入功能。
awk程序通常被放置在可以对它们进行测试和修改的文件中。用脚本文件调用awk的语法如下:
|
|
-f选项的工作方式与在sed中相同。
尽管awk指令与sed指令的结构相同,都由模式和过程两部分组成,但过程本身有很大不同。awk看上去不像编辑器而更像一种程序设计语言。语句和函数取代了使用一个或两个字符组成的命令序列。例如,使用print语句打印表达式的值或打印当前输入行的内容。
在通常情况下,awk将每个输入行解释为一条记录而将那一行上的每个单词(由空格或制表符分隔)解释为一个字段(可以改变这些默认设置)。一个或多个连续的空格或制表符被看做一个定界符。awk允许在模式或过程中引用这些字段。$0代表整个输入行。$1、$2..…表示输入行上的各个字段。应用脚本之前,awk先拆分输入记录。我们来看几个示例,使用样本输入文件list。
第一个示例包含单个指令,用于打印输入文件中每行的第一个字段。
“$1”表示每个输入行上的第一个字段的值。因为这里没有指定模式,所以打印语句应用于所有的行。下一个示例指定了“/MA/”模式,但是其中没有过程。这个默认操作是打印匹配这种模式的每一行。
本例打印了3行。正如在第一章所提到的那样,awk程序更像一种查询语言,从文件中提取有用的信息。可以认为以上模式指定了一种条件,用于选择要包括在报表中的记录,也就是这些记录必须包含字符串"MA"。现在还可以指定记录的哪些部分要包括在报表中。下一个示例使用一条print语句限制只输出每条记录的第一个字段。
如果尝试着大声地阅读这一句话:Print the first word of each line containing the string"MA",这将有助于我们理解上面的指令。可以把它说成"word(单词)",这是因为在默认情况下,awk使用空格或制表符作为字段分隔符将输入分隔成字段。
在下一个示例中,使用- F选项将字段分隔符改变为逗号。它使我们能够检索3个字段中的任一个:全称、街道地址或城市和州。
不要将改变字段分隔符的 -F选项与指定脚本文件名的 -f选项弄混。
下一个示例将每个字段单独打印在一行上。多重命令由分号隔开。
|
|
前面的示例使用sed改变了输入数据的内容。使用awk重新排列数据。在上面的awk示例中,注意如何将前导空白看做是第二个和第三个字段的一部分。
出错信息
当遇到程序中的问题时,awk的每个实现都会给出不同的出错信息。因此,这里不引用特殊版本的消息;当出现问题时它是显而易见的。下面任何一种情况都会产生消息:
- 没有用大括号({})将过程括起来
- 没有用单引号(’’)将指令括起来
- 没有用斜杠(//)中将正则表达式括起来
选项总结
表2- 2汇总了awk命令行的选项。
表2-2:awk的命令行选项
| 选项 | 描述 |
|---|---|
| -f | 跟随脚本的文本名 |
| -F | 改变字段分隔符 |
| -v | 跟随 var=value |
在命令行上指定的参数中的- v选项将在第七章"编写awk脚本"中讨论。
同时使用 sed 和 awk
在 UNIX 中, 管道可用于将一个程序的输出作为输入传递给另一个程序。参看几个综合使用 sed 和 awk 来产生报表的示例。用州的全名代替邮政编码的 sed 脚本通常已经足够, 它可被再次用做名为 nameState 的脚本文件:
当然, 要处理所有的州, 而不只是这 5 个, 并且如果是对文档而不是邮件列表执行这些命令, 则要确保不会生成不必要的替换。
该程序 (使用输入文件 list) 的输出, 与我们已经看到的相同。在下一个示例中, 由 nameState 产生的输出被输送到一个 awk 程序中, 这个 awk 程序用于从每条记录中提取州的名字。
以上 awk 程序处理由 sed 脚本产生的输出。记住前面的 sed 脚本用逗号和州的全称代替缩写。实际上, 它将包含城市和州的第三个字段拆分成两个字段。“$4”表示第四个字段。
这里所做的事情可以完全由 sed 完成, 但可能会有更多的困难和更少的通用性。而且, 因为 awk 允许替换匹配的字符串, 所以可以完全用 awk 脚本得到这个结果。
本程序的结果不是非常有用, 可以将它传递给 sort | uniq -c, 它将州名按字母表排序, 同时给出每个州名出现的次数。
现在, 我们要做一些更有趣的事情。按州的名字排序并列出州的名字, 以及住在那个州的人的名字。下面的示例展示了 byState 程序:
这个 shell 脚本有 3 个部分。程序中调用 awk 以产生 sort 程序的输入, 然后再次调用 awk 测试排好序的输入, 并确定当前记录中的州的名字, 是否与前一个记录中的名字相同。我们来看这个脚本的执行:
这些名字被按州排序。这是使用 awk 从结构化数据中生成报表的典型示例。
为了检查byState程序是如何工作的,让我们分别看看每个部分。它被设计为从nameState程序读取输入并期待“$4”成为州的名字。查看由程序的第一行产生的输出:
|
|
默认情况下, sort 程序按字母顺序排列行, 从左到右查看字符。为了按州 (而不是名字) 对记录进行排序, 我们将州作为排序的关键字插入到记录的开始处。现在, sort 程序可以工作了 (注意使用 sort 实用工具可以避免在 awk 内部编写排序程序)。
第二次调用 awk 时执行程序设计任务。脚本查看每条记录的第一个字段以决定它是否与前一条记录相同。如果不相同, 则同时打印州的名字和人的名字。如果相同, 则只打印人的名字。
这里还有几个重要的事情, 包括给一个变量赋值, 测试每个输入行的第一个字段来看它是否包含一个变量字符串, 并且打印制表符来调整输出数据的对齐。注意在使用某个变量之前不必对它先赋值 (因为 awk 将变量初始化为空字符串)。这是较小的脚本, 但是在第十二章 “综合应用” 中的更大的索引程序中, 可以看到用于比较索引条目的类似的程序。然而, 目前可以先不用过多地关心有关每条语句做什么事情。这里的目的是给你一个 sed 和 awk 能做什么的概述。
本章概述了有关 sed 和 awk 的基本操作。介绍了重要的命令行选项以及脚本的使用。下一章将介绍正则表达式, 即两个程序用来在输入中进行模式匹配的某些内容。
文章作者 会写代码的小郎中
上次更新 2011-08-01
许可协议 CC BY-NC-ND 4.0