Awk命令

一. AWK入门指南

Awk是一种便于使用且表达能力强的程序设计语言,可应用于各类计算和数据处理任务。本章是个入门指南,让你可以尽快地开始编写你本身的程序。第二章将描述整个语言,而剩下的章节将向你展现如何使用Awk来解决许多不一样方面的问题。纵观全书,咱们尽可能选择了一些对你有用、有趣而且有指导意义的实例。正则表达式

1.1 起步

有用的awk程序每每很简短,仅仅一两行。假设你有一个名为 emp.data 的文件,其中包含员工的姓名、薪资(美圆/小时)以及小时数,一个员工一行数据,以下所示:shell

Beth 4.00 0
Dan 3.75 0
kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18

如今你想打印出工做时间超过零小时的员工的姓名和工资(薪资乘以时间)。这种任务对于awk来讲就是小菜一碟。输入这个命令行就能够了::编程

awk '$3 >0 { print $1, $2 * $3 }' emp.data 

你应该会获得以下输出:数组

Kathy 40
Mark 100
Mary 121
Susie 76.5

该命令行告诉系统执行引号内的awk程序,从输入文件 emp.data 获取程序所需的数据。引号内的部分是个完整的awk程序,包含单个模式-动做语句。模式 $3>0 用于匹配第三列大于0的输入行,动做:编程语言

{ print $1, $2 * $3 } 

打印每一个匹配行的第一个字段以及第二第三字段的乘积。函数

若是你想打印出还没工做过的员工的姓名,则输入命令行::工具

awk '$3 == 0 { print $1 }' emp.data 

这里,模式 $3 == 0 匹配第三个字段等于0的行,动做:测试

{ print $1 } 

打印该行的第一个字段。atom

当你阅读本书时,应该尝试执行与修改示例程序。大多数程序都很简短,因此你能快速理解awk是如何工做的。在Unix系统上,以上两个事务在终端里看起来是这样的:url

$ awk ‘$3 > 0 { print $1, $2 * $3 }’ emp.data
Kathy 40
Mark 100
Mary 121
Susie 76.5
$ awk ‘$3 == 0 { print $1 }’ emp.data
Beth
Dan
$

行首的 $ 是系统提示符,也许在你的机器上不同。

AWK程序的结构

让咱们回头看一下到底发生了什么事情。上述的命令行中,引号之间的部分是awk编程语言写就的程序。本章中的每一个awk程序都是一个或多个模式-动做语句的序列:

pattern { action }
pattern { action }
...

awk的基本操做是一行一行地扫描输入,搜索匹配任意程序中模式的行。词语“匹配”的准确意义是视具体的模式而言,对于模式 $3 >0 来讲,意思是“条件为真”。

每一个模式依次测试每一个输入行。对于匹配到行的模式,其对应的动做(也许包含多步)获得执行,而后读取下一行并继续匹配,直到全部的输入读取完毕。

上面的程序都是模式与动做的典型示例。:

$3 == 0 { print $1 } 

是单个模式-动做语句;对于第三个字段为0的每行,打印其第一个字段。

模式-动做语句中的模式或动做(但不是同时二者)均可以省略。若是某个模式没有动做,例如::

$3 == 0 

那么模式匹配到的每一行(即,对于该行,条件为真)都会被打印出来。该程序会打印 emp.data 文件中第三个字段为0的两行

Beth 4.00 0
Dan 3.75 0

若是有个没有模式的动做,例如::

{ print $1 } 

那么这种状况下的动做会打印每一个输入行的第一列。

因为模式和动做二者任一都是可选的,因此须要使用大括号包围动做以区分于其余模式。

执行AWK程序

执行awk程序的方式有多种。你能够输入以下形式的命令行::

awk 'program' input files 

从而在每一个指定的输入文件上执行这个program。例如,你能够输入::

awk '$3 == 0 { print $1 }' file1 file2 

打印file1和file2文件中第三个字段为0的每一行的第一个字段。

你能够省略命令行中的输入文件,仅输入::

awk 'program' 

这种状况下,awk会将program应用于你在终端中接着输入的任意数据行,直到你输入一个文件结束信号(Unix系统上为control-d)。以下是Unix系统的一个会话示例:

$ awk ‘$3 == 0 { print $1 }’
Beth 4.00 0

Beth

Dan 3.75 0

Dan

Kathy 3.75 10
Kathy 3.75 0

Kathy

...

加粗的字符是计算机打印的。

这个动做很是便于尝试awk:输入你的程序,而后输入数据,观察发生了什么。咱们再次鼓励你尝试这些示例并进行改动。

注意命令行中的程序是用单引号包围着的。这会防止shell解释程序中 $ 这样的字符,也容许程序的长度超过一行。

当程序比较短小(几行的长度)的时候,这种约定会很方便。然而,若是程序较长,将程序写到一个单独的文件中会更加方便。假设存在程序 progfile ,输入命令行::

awk -f progfile optional list of input files 

其中 -f 选项指示awk从指定文件中获取程序。可使用任意文件名替换 progfile 。

错误

若是你的awk程序存在错误,awk会给你一段诊断信息。例如,若是你打错了大括号,以下所示::

awk '$3 == 0 [ print $1 }' emp.data 

你会获得以下信息:

awk: syntax error at source line 1
context is
$3 == 0 >>> [ <<<
extra }
missing ]
awk: bailing out at source line 1

“Syntax error”意味着在 >>> <<< 标记的地方检测到语法错误。“Bailing out”意味着没有试图恢复。有时你会获得更多的帮助-关于错误是什么,好比大括号或括弧不匹配。

由于存在句法错误,awk就不会尝试执行这个程序。然而,有些错误,直到你的程序被执行才会检测出来。例如,若是你试图用零去除某个数,awk会在这个除法的地方中止处理并报告输入行的行号以及在程序中的行号(这话是什么意思?难道输入行的行号是忽略空行后的行号?)。

1.2 简单输出

这一节接下来的部分包含了一些短小,典型的awk程序,基于操纵上文中提到的 emp.data 文件. 咱们会简单的解释程序在作什么,但这些例子主要是为了介绍 awk 中常见的一些简单有用的操做 – 打印字段, 选择输入, 转换数据. 咱们并 没有展示 awk 程序能作的全部事情, 也并不打算深刻的去探讨例子中的一些细节. 但在你读完这一节以后, 你将可以完成一些简单的任务, 而且你将发如今阅读后 面章节的时候会变的容易的多.

咱们一般只会列出程序部分, 而不是整个命令行. 在任何状况下, 程序均可以用 引号包含起来放到 awk 命令的地一个参数中运行, 就像上文中展现的那样, 或者 把它放到一个文件中使用 awk 的 -f 参数调用它.

在 awk 中仅仅只有两种数据类型: 数值 和 字符构成的字符串. emp.data 是 一个包含这类信息的典型文件 – 混合了被空格和(或)制表符分割的数字和词语.

Awk 程序一次从输入文件的中读取一行内容并把它分割成一个个字段, 一般默认 状况下, 一个字段是一个不包含任何空格或制表符的连续字符序列. 当前输入的 行中的地一个字段被称作 $1, 第二个是 $2, 以此类推. 整个行的内容被定 义为 $0. 每一行的字段数量能够不一样.

一般, 咱们要作的仅仅只是打印出每一行中的某些字段, 也许还要作一些计算. 这一节的程序基本上都是这种形式.

打印每一行

若是一个动做没有任何模式, 这个动做会对全部输入的行进行操做. print 语 句用来打印(输出)当前输入的行, 因此程序

{ print } 

会输出全部输入的内容到标准输出. 因为 $0 表示整行,

{ print $0 } 

也会作同样的事情.

打印特定字段

使用一个 print 语句能够在同一行中输出不止一个字段. 下面的程序输出了每 行输入中的第一和第三个字段

{ print $1, $3 } 

使用 emp.data 做为输入, 它将会获得

Beth 0 Dan 0 Kathy 10 Mark 20 Mary 22 Susie 18 

在 print 语句中被逗号分割的表达式, 在默认状况下他们将会用一个空格分割 来输出. 每一行 print 生成的内容都会以一个换行符做为结束. 但这些默认行 为均可以自定义; 咱们将在第二章中介绍具体的方法.

NF, 字段数量

很显然你可能会发现你老是须要经过 $1, $2 这样来指定不一样的字段, 但任何表 达式均可以使用在$以后来表达一个字段的序号; 表达式会被求值并用于表示字段 序号. Awk会对当前输入的行有多少个字段进行计数, 而且将当前行的字段数量存 储在一个内建的称做 NF 的变量中. 所以, 下面的程序

{ print NF, $1, $NF } 

会依次打印出每一行的字段数量, 第一个字段的值, 最后一个字段的值.

计算和打印

你也能够对字段的值进行计算后再打印出来. 下面的程序

{ print $1, $2 * $3 } 

是一个典型的例子. 它会打印出姓名和员工的合计支出(以小时计算):

Beth 0 Dan 0 Kathy 40 Mark 100 Mary 121 Susie 76.5 

咱们立刻就会学到怎么让这个输出看起来更漂亮.

打印行号

Awk提供了另外一个内建变量, 叫作 NR, 它会存储当前已经读取了多少行的计数. 咱们可使用 NR 和 $0 给 emp.data 的没一行加上行号:

{ print NR, $0 } 

打印的输出看起来会是这样:

1 Beth 4.00 0 2 Dan 3.75 0 3 Kathy 4.00 10 4 Mark 5.00 20 5 Mary 5.50 22 6 Susie 4.25 1 8 

在输出中添加内容

你固然也能够在字段中间或者计算的值中间打印输出想要的内容:

{ print "total pay for", $1, "is", $2 * $3 } 

输出

total pay for Beth is 0 total pay for Dan is 0 total pay for Kathy is 40 total pay for Mark is 100 total pay for Mary is 121 total pay for Susie is 76.5 

在打印语句中, 双引号内的文字将会在字段和计算的值中插入输出.

1.3 高级输出

print 语句可用于快速而简单的输出。若要严格按照你所想的格式化输出,则须要使用 printf 语句。正如我将在2.4节所见, printf 几乎能够产生任何形式的输出,但在本节中,咱们仅展现其部分功能。

字段排队

printf 语句的形式以下::

printf(format, value1, value2, ..., valuen) 

其中 format 是字符串,包含要逐字打印的文本,穿插着 format 以后的每一个值该如何打印的规格(specification)。一个规格是一个 % 符,后面跟着一些字符,用来控制一个 value 的格式。第一个规格说明如何打印 value1 ,第二个说明如何打印 value2 ,... 。所以,有多少 value 要打印,在 format 中就要有多少个 % 规格。

这里有个程序使用 printf 打印每位员工的总薪酬::

{ printf("total pay for %s is $%.2f\n", $1, $2 * $3) } 

printf 语句中的规格字符串包含两个 % 规格。第一个是 %s ,说明以字符串的方式打印第一个值 $1 。第二个是 %.2f ,说明以数字的方式打印第二个值 $2*$3 ,并保留小数点后面两位。规格字符串中其余东西,包括美圆符号,仅逐字打印。字符串尾部的 \n 表明开始新的一行,使得后续输出将从下一行开始。以 emp.data 为输入,该程序产生:

total pay for Beth is $0.00 total pay for Dan is $0.00 total pay for Kathy is $40.00 total pay for Mark is $100.00 total pay for Mary is $121.00 total pay for Susie is $76.50 

printf 不会自动产生空格或者新的行,必须是你本身来建立,因此不要忘了 \n 。

另外一个程序是打印每位员工的姓名与薪酬::

{ printf("%-8s $%6.2f\n", $1, $2 * $3) } 

第一个规格 %-8s 将一个姓名以字符串形式在8个字符宽度的字段中左对齐输出。第二个规格 %6.2f 将薪酬以数字的形式,保留小数点后两位,在6个字符宽度的字段中输出。

Beth     $ 0.00 Dan $ 0.00 Kathy $ 40.00 Mark $100.00 Mary $121.00 Susie $ 76.50 

以后咱们将展现更多的 printf 示例。一切精彩尽在2.4小节。

排序输出

假设你想打印每位员工的全部数据,包括他或她的薪酬,并以薪酬递增的方式进行排序输出。最简单的方式是使用awk将每位员工的总薪酬置于其记录以前,而后利用一个排序程序来处理awk的输出。Unix上,命令行以下:

awk '{ printf("%6.2f %s\n", $2 * $3, $0) }' emp.data | sort 

将awk的输出经过管道传给 sort 命令,输出为:

  0.00    Beth 4.00 0 0.00 Dan 3.75 0 40.00 Kathy 4.00 10 76.50 Susie 4.25 18 100.00 Mark 5.00 20 121.00 Mary 5.50 22 

1.4 选择

Awk的模式适合用于为进一步的处理从输入中选择相关的数据行。因为不带动做的模式会打印全部匹配模式的行,因此不少awk程序仅包含一个模式。本节将给出一些有用的模式示例。

经过对比选择

这个程序使用一个对比模式来选择每小时赚5美圆或更多的员工记录,也就是,第二个字段大于等于5的行::

$2 >= 5 

从 emp.data 中选出这些行::

Mark    5.00 20 Mary 5.50 22 

经过计算选择

程序

$2 * $3 > 50 { printf("$%.2f for %s\n", $2 * $3, $1) } 

打印出总薪资超过50美圆的员工的薪酬。

经过文本内容选择

除了数值测试,你还能够选择包含特定单词或短语的输入行。这个程序会打印全部第一个字段为 Susie 的行::

$1 == "Susie" 

操做符 == 用于测试相等性。你也可使用称为 正则表达式 的模式查找包含任意字母组合,单词或短语的文本。这个程序打印任意位置包含 Susie 的行::

/Susie/

输出为这一行::

Susie   4.25 18 

正则表达式可用于指定复杂的多的模式;2.1节将会有全面的论述。

模式组合

可使用括号和逻辑操做符与 && , 或 || , 以及非 ! 对模式进行组合。程序:

$2 >= 4 || $3 >= 20 

会打印 $2 (第二个字段) 大于等于 4 或者 $3 (第三个字段) 大于等于 20 的行::

Beth    4.00 0 kathy 4.00 10 Mark 5.00 20 Mary 5.50 22 Susie 4.25 18 

两个条件都知足的行仅打印一次。与以下包含两个模式程序相比::

$2 >= 4 $3 >= 20 

若是某个输入行两个条件都知足,这个程序会打印它两遍::

Beth    4.00 0 Kathy 4.00 10 Mark 5.00 20 Mark 5.00 20 Mary 5.50 22 Mary 5.50 22 Susie 4.25 18 

注意以下程序:

!($2 < 4 && $3 < 20) 

会打印极不知足 $2 小于4也不知足 $3 小于20的行;这个条件与上面第一个模式组合等价,虽然也许可读性差了点。

数据验证

实际的数据中老是会存在错误的。在数据验证-检查数据的值是否合理以及格式是否正确-方面,Awk是个优秀的工具。

数据验证本质上是否认的:不是打印具有指望属性的行,而是打印可疑的行。以下程序使用对比模式 将5个数据合理性测试应用于 emp.data 的每一行::

NF != 3 { print $0, "number of fields is not equal to 3" } $2 < 3.35 { print $0, "rate is below minimum wage" } $2 > 10 { print $0, "rate exceeds $10 per hour" } $3 < 0 { print $0, "negative hours worked" } $3 > 60 { print $0, "too many hours worked" } 

若是没有错误,则没有输出。

BEGIN与END

特殊模式 BEGIN 用于匹配第一个输入文件的第一行以前的位置, END 则用于匹配处理过的最后一个文件的最后一行以后的位置。这个程序使用 BEGIN 来输出一个标题::

BEGIN { print "Name RATE HOURS"; print ""} { print } 

输出为::

NAME    RATE HOURS Beth 4.00 0 Dan 3.75 0 Kathy 4.00 10 Mark 5.00 20 Mary 5.50 22 Susie 4.25 18 

程序的动做部分你能够在一行上放多个语句,不过要使用分号进行分隔。注意 普通的 print 是打印当前输入行,与之不一样的是 print “” 会打印一个空行。

1.5 使用AWK进行计算

一个动做就是一个以新行或者分号分隔的语句序列。你已经见过一些其动做仅是单个 print 语句的例子。本节将提供一些执行简单的数值以及字符串计算的语句示例。在这些语句中,你不只可使用像 NF 这样的内置变量,还能够建立本身的变量用于计算、存储数据诸如此类的操做。awk中,用户建立的变量不须要声明。

计数

这个程序使用一个变量 emp 来统计工做超过15个小时的员工的数目::

$3 > 15 { emp = emp + 1 } END { print emp, "employees worked more than 15 hours" } 

对于第三个字段超过15的每行, emp 的前一个值加1。以 emp.data 为输入,该程序产生::

3 employees worked more than 15 hours 

用做数字的awk变量的默认初始值为0,因此咱们不须要初始化 emp 。

求和与平均值

为计算员工的数目,咱们可使用内置变量 NR ,它保存着到目前位置读取的行数;在全部输入的结尾它的值就是所读的全部行数。

END { print NR, "employees" } 

输出为::

6 employees 

以下是一个使用 NR 来计算薪酬均值的程序::

    { pay = pay + $2 * $3 } END { print NR, "employees" print "total pay is", pay print "average pay is", pay/NR } 

第一个动做累计全部员工的总薪酬。 END 动做打印出

6 employees total pay is 337.5 average pay is 56.25 

很明显, printf 可用来产生更简洁的输出。而且该程序也有个潜在的错误:在某种不太可能发生的状况下, NR 等于0,那么程序会试图执行零除,从而产生错误信息。

处理文本

awk的优点之一是能像大多数语言处理数字同样方便地处理字符串。awk变量能够保存数字也能够保存字符串。这个程序会找出时薪最高的员工::

$2 > maxrate { maxrate = $2; maxemp = $1 } END { print "highest hourly rate:", maxrate, "for", maxemp } 

输出

highest hourly rate: 5.50 for Mary

这个程序中,变量 maxrate 保存着一个数值,而变量 maxemp 则是保存着一个字符串。(若是有几个员工都有着相同的最大时薪,该程序则只找出第一个。)

字符串链接

能够合并老字符串来建立新字符串。这种操做称为 链接(concatenation) 。程序

    { names = names $1 " "} END { print names } 

经过将每一个姓名和一个空格附加到变量 names 的前一个值, 来将全部员工的姓名收集进单个字符串中。最后 END 动做打印出 names 的值::

Beth Dan Kathy Mark Mary Susie 

awk程序中,链接操做的表现形式是将字符串值一个接一个地写出来。对于每一个输入行,程序的第一个语句先链接三个字符串: names 的前一个值、当前行的第一个字段以及一个空格,而后将获得的字符串赋值给 names 。所以,读取全部的输入行以后, names 就是个字符串,包含全部员工的姓名,每一个姓名后面跟着一个空格。用于保存字符串的变量的默认初始值是空字符串(也就是说该字符串包含零个字符),所以这个程序中的 names 不须要显式初始化。

打印最后一个输入行

虽然在 END 动做中 NR 还保留着它的值,但 $0 没有。程序

    { last = $0 } END { print last } 

是打印最后一个输入行的一种方式::

Susie   4.25 18 

内置函数

咱们已看到awk提供了内置变量来保存某些频繁使用的数量,好比:字段的数量和输入行的数量。相似地,也有内置函数用来计算其余有用的数值。除了平方根、对数、随机数诸如此类的算术函数,也有操做文本的函数。其中之一是 length ,计算一个字符串中的字符数量。例如,这个程序会计算每一个人的姓名的长度::

{ print $1, length($1) } 

结果::

Beth 4 Dan 3 Kathy 5 Mark 4 Mary 4 Susie 5 

行、单词以及字符的计数

这个程序使用了 length 、 NF 、以及 NR 来统计输入中行、单词以及字符的数量。为了简便,咱们将每一个字段看做一个单词。

    { nc = nc + length($0) + 1 nw = nw + NF } END { print NR, "lines,", nw, "words,", nc, "characters" } 

文件 emp.data 有:

6 lines, 18 words, 77 characters 

$0 并不包含每一个输入行的末尾的换行符,因此咱们要另外加个1。

1.6 控制语句

Awk为选择提供了一个 if-else 语句,以及为循环提供了几个语句,因此都效仿C语言中对应的控制语句。它们仅能够在动做中使用。

if-else语句

以下程序将计算时薪超过6美圆的员工的总薪酬与平均薪酬。它使用一个 if 来防范计算平均薪酬时的零除问题。

$2 > 6 { n = n + 1; pay = pay + $2 * $3 } END { if (n > 0) print n, "employees, total pay is", pay, "average pay is", pay/n else print "no employees are paid more than $6/hour" } 

emp.data 的输出是::

no employees are paid more than $6/hour 

if-else 语句中,if 后的条件会被计算。若是为真,执行第一个 print 语句。不然,执行第二个 print 语句。注意咱们可使用一个逗号将一个长语句截断为多行来书写。

while语句

一个 while 语句有一个条件和一个执行体。条件为真时执行体中的语句会被重复执行。这个程序使用公式 value=amount(1+rate)yearsvalue=amount(1+rate)years

来演示以特定的利率投资必定量的钱,其数值是如何随着年数增加的。

# interest1 - 计算复利
# 输入: 钱数 利率 年数 # 输出: 复利值 { i = 1 while (i <= $3) { printf("\t%.2f\n", $1 * (1 + $2) ^ i) i = i + 1 } } 

条件是 while 后括弧包围的表达式;循环体是条件后大括号包围的两个表达式。 printf 规格字符串中的 \t 表明制表符; ^ 是指数操做符。从 # 开始到行尾的文本是注释,会被awk忽略,但能帮助程序的读者理解程序作的事情。

你能够为这程序输入三个一组的数字,看看不同的钱数、利率、以及年数会产生什么。例如,以下事务演示了1000美圆,利率为6%与12%,5年的复利分别是如何增加的::

$ awk -f interest1 1000 .06 5 1060.00 1123.60 1191.02 1262.48 1338.23 1000 .12 5 1120.00 1254.40 1404.93 1573.52 1762.34 

for语句

另外一个语句, for ,将大多数循环都包含的初始化、测试、以及自增压缩成一行。以下是以前利息计算的 for 版本::

# interest1 - 计算复利
# 输入: 钱数 利率 年数 # 输出: 每一年末的复利 { for (i = 1; i <= $3; i = i + 1) printf("\t%.2f\n", $1 * (1 + $2) ^ i) } 

初始化 i = 1 只执行一次。接下来,测试条件 i <= $3 ;若是为真,则执行循环体的 printf 语句。循环体执行结束后执行自增 i = i + 1 ,接着由另外一次条件测试开始下一个循环迭代。代码更加紧凑,而且因为循环体仅是一条语句,因此不须要大括号来包围它。

1.7 数组

awk为存储一组相关的值提供了数组。虽然数组给予了awk很强的能力,但在这里咱们仅展现一个简单的例子。以下程序将按行逆序打印输入。第一个动做将输入行存为数组 line 的连续元素;即第一行放在 line[1] ,第二行放在 line[2] , 依次继续。 END 动做使用一个 while 语句从后往前打印数组中的输入行::

# 反转 - 按行逆序打印输入

    { line[NR] = $0 } # 记下每一个输入行 END { i = NR # 逆序打印 while (i > 0) { print line[i] i = i - 1 } } 

以 emp.data 为输入,输出为

Susie    4.25 18 Mary 5.50 22 Mark 5.00 20 Kathy 4.00 10 Dan 3.75 0 Beth 4.00 0 

以下是使用 for 语句实现的相同示例::

# 反转 - 按行逆序打印输入

    { line[NR] = $0 } # 记下每一个输入行 END { for (i = NR; i > 0; i = i - 1) print line[i] }
相关文章
相关标签/搜索