三剑客之awk

简介

  • Awk 分别代表其三个作者姓氏的第一个字母: Alfred Aho 、Peter Weinberger、Brain Kernighan
  • 目前在Linux中常用的是 awk 编译版本有 mawk 、gawk
  • 以 RedHat 为代表使用的是 gawk,以Ubuntu为代表使用的是 mawk;CentOS 当然也用的是gawk
  • gawk 是GUN Project 的 awk解释器的开源代码实现

 

工作原理

  1. Awk 是被设计用于文本处理,并通常被用作数据提取和报告工具的解释性程序设计语言
  2. awk如果未指定动作,则所有匹配该模式的行都显示在屏幕上;如果只指定动作而未定义模式,会对所有输入行执行指定动作。
  3. awk使用一行作为输入,并将这一行赋予内部变量$0,默认时每一行也可以称为一个记录,以换行符结束。
  4. awk每一行根据FS这个内部变量来进行列的划分,每一列为一个变量,$1等于Tom,$2等于Savage,一次类推,最多可以叠加到100
  5. awk打印字段时,将采用print函数
  6. awk每输出一行之后,将从文本输入获取另外一行,并将其存储至$0,覆盖原来的内容,重新将新的行进行分列,并进行处理,重复该操作,一直到最后一行。

执行格式

  • awk程序由awk命令、括在引号(或者写在文件)中的程序指令以及输入文件的文件名几个部分组成;
  • awk如果没有指定输入文件,输入则来自标准输入(stdin),即键盘。
  • awk指令由模式、操作、或者两者的组成。
  • awk模式由某种类型的表达式组成的语句。模式不能被括在大括号中,由括在两个正斜杠之间的正则表达式、或者多个awk操作符组成的表达式组成。如果某个表达式中没有出现关键词if,但实际计算时却暗含if这个词,那么这个表达式就是模式。
  • awk操作由括在大括号内的一条或者多条语句组成,语句之间用分号或者换行符隔开。
  • awk命令可以在命令行输入,也可以写入awk脚本中。
  • awk要处理的文本则来自文件、管道或者标准输入。
awk从文件输入内容格式
awk 'pattern' filename
awk '{action}' filename
awk 'pattern{action}' filename

 

awk从管道输入内容格式
command | awk 'pattern'
command | awk '{action}'
command | awk 'pattern{action}'

 

记录与字段

  • 记录NR:默认情况下,每一行称为一条记录,以换行符结束。
  • 记录分隔符:默认情况下,输入和输出记录的分隔符(行分隔符)都是回车符(换行符),分别保存在awk的内置变量ORS和RS中。ORS和RS的值都可以修改,但是只能以特定方式进行修改。
  • 变量$0:awk用$0代指当前处理的整条记录(当$0因替换或赋值而改变时,NF的值,即字段的数目也可能随之改变)。换行符的值保存在awk的内置变量RS中,其默认值即回车。
  • 变量NR:每条记录的记录号都保存在awk的内置变量NR中。每处理完一条记录,NR的值都会加1。
  • 字段NF:每条记录都是由字段(field)组成,默认情况下,字段间用空格符(即空格或者制表符)分隔。每一个被分隔的词称之为字段,awk在内置变量NF中保存记录的字段数。NF的值因行而异,其上限与具体的版本的实现相关,通常每一行最多100个字段。可以创建新的字段。
  • 输入字段分隔符:输入字段分隔符awk的内置变量FS中保存了输入字段分隔符的值。使用FS的默认值时,awk用空格或者制表符来分隔字段,并且删除各字段前多余的空格或者制表符。可以通过在BEGIN语句中或者命令行上赋值来改变FS的值。在命令行上改变FS的值需要使用-F选项,后面指定代表新分隔符的字符。
  • 输出字段分隔符:默认的输出字段分隔符是单个空格,被保存于awk的内置变量OFS中。在print分隔的时候,用逗号表示。如果没有用逗号分隔,则输出结果中的字段将堆叠到一起,除非字段间有逗号分隔,否则输出结果的字段间不会加上OFS的值。另外OFS的值也可以改变。
示例

1、awk的变量$0保存当前记录的内容,它会被打印到屏幕上

sh-4.2# cat employees 
Tom Jones   4424    5/12/66 543354
Mary Adams  5346    11/4/63 28765
Sally Chang 1654    7/22/54 650000
Billy Black 1683    9/23/44 336500
sh-4.2# awk '{print $0}' employees 
Tom Jones   4424    5/12/66 543354
Mary Adams  5346    11/4/63 28765
Sally Chang 1654    7/22/54 650000
Billy Black 1683    9/23/44 336500

或者
sh-4.2# awk '{print}' employees 
Tom Jones   4424    5/12/66 543354
Mary Adams  5346    11/4/63 28765
Sally Chang 1654    7/22/54 650000
Billy Black 1683    9/23/44 336500
说明:默认awk只有指定操作没有指定模式,将对所有行进行打印操作

 

2、照源文件打印出每一条记录,并且在行首加上了它的记录号(NR)

sh-4.2# awk '{print NR,$0}' employees 
1 Tom Jones     4424    5/12/66 543354
2 Mary Adams    5346    11/4/63 28765
3 Sally Chang   1654    7/22/54 650000
4 Billy Black   1683    9/23/44 336500

 

3、打印文件中每一行的行号、第一字段、第二字段、第五字段

sh-4.2# awk '{print NR,$1,$2,$5}' employees 
1 Tom Jones   543354
2 Mary Adams  28765
3 Sally Chang 650000
4 Billy Black 336500

 

4、打印文件中每一行记录,后面跟上该记录的字段数

sh-4.2# awk '{print $0,NF}' employees 
Tom Jones   4424    5/12/66 543354 5
Mary Adams  5346    11/4/63 28765  5
Sally Chang 1654    7/22/54 650000 5
Billy Black 1683    9/23/44 336500 5

 

5、如何使用-F选项在命令行中改变字段分隔符

sh-4.2# cat employees
Tom Jones:4424:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Billy Black:1683:9/23/44:336500

sh-4.2# awk -F':' '/Tom Jones/{print $1,$2}'  employees
Tom Jones 4424

 

6、选项-F与正则表达式的结合使用

sh-4.2# awk -F '[ :\t]' '{print $1,$2,$3}' employees 
Tom Jones 4424
Mary Adams 5346
Sally Chang 1654
Billy Black 1683

说明:-F选项后面跟了一个位于中括号里面的正则表达式。当遇到空格、冒号、或者制表符时,awk会把它们都当成字段分隔符。这个表达式两头都加了单引号,这样就不会被shell当成自己的元字符来解释。

 

print函数与printf函数

print的作用
简单打印指定内容

 

print的描述
print函数的参数可以是变量、数值、或者字符串常量。
print函数的参数为字符串则必须用双引号引起来。
print函数的参数有多个的情况下,用逗号进行分隔;如果没有分隔,则所有的内容将堆叠到一起。

 

print的选项
转义序列 含义
\b      退格
\f      换页
\n      换行
\r      回车
\t      制表
\047    八进制
\c      C代表任意一其他字符,例如”\”

 

示例
sh-4.2# date | awk '{print "Month:" $2 "\nYear:",$6}'
Month:Nov
Year: 2018

sh-4.2# awk '/Sally/{print "\t\tHave a nice day," $1,$2 "!"}' employees 
    Have a nice day,Sally Chang!

 

printf的作用
格式化特别的输出

 

printf的描述
  • 打印输出时,可能需要指定字段间的空格数,从而把列排版整齐。在print函数中使用制表符并不能保证得到想要的输出结果,因此可以用printf函数来格式化特别的输出。
  • printf函数返回一个带格式的字符串给标准输出,如同C语言中的printf语句一样。printf语句包括一个加引号的控制串,控制串中可能嵌入若干格式说明和修饰符。控制串后面跟一个逗号,之后是一系列逗号分隔的表达式。printf函数根据控制串中的说明编排这些表达式的格式。与print函数不同的是,printf不会在行尾自动换行。因此,如果要换行,就必须在控制串中提供转义字符\n。
  • 每一个百分号和格式说明都必须有一个对应的变量。要打印百分号就必须在控制串中给出两个百分号。
printf的参数
转义字符 定义
c   字符
s   字符串
d   十进制整数
ld  十进制长整数
u   十进制无符号整数
lu  十进制无符号长整数
x   十六进制整数
lx  十六进制长整数
o   八进制整数
lo  八进制长整数
e   用科学记数法(e 记数法)表示的浮点数
f   浮点数
g   选用e或f中较短的一种形式

修饰符 定义
-   左对齐修饰符
#   显示8 进制整数时在前面加个0显示16 进制整数时在前面加0x
+   显示使用d 、e 、f 和g 转换的整数时,加上正负号+或-
0   用0而不是空白符来填充所显示的值

格式说明符 功能
%c  打印单个ASCII 字符printf("The character is %c\n",x)输出: The character is A
%d  打印一个十进制数printf("The boy is %d years old\n",y)输出:The boy is 15 years old
%e  打印数字的e 记数法形式printf("z is %e\n",z) 打印: z is 2.3e+0 1
%f  打印一个浮点数printf("z is %f\n", 2.3 * 2)输出: z is 4.600000
%o  打印数字的八进制printf("y is %o\n",y)输出:z is 17
%s  打印一个字符串print("The name of the culprit is %s\n",$1)输出:The name of the culprit is Bob Smith
%x  打印数字的十六进制值printf("y is %x\n",y)输出:x is f

打印变量时,输出所在的位置称为""(field),域的宽度(width)是指该域中所包含的字符个数。下面这些例子中, printf控制串里的管道符(竖杠)是文本的一部分, 用于指示格式的起始与结束。

 

示例
$ echo "Linux" | awk '{printf "|%-15s|\n",$1}'
|Linux          |

说明:对于echo命令的输出,Linux是经管道发给awk。printf函数包含一个控制串。百分号让printf做好准备,它要打印一个占15个格、向左对齐的字符串,这个字符串夹在两个竖杠之间,并且以换行符结尾。百分号后的短划线表示左对齐。控制串后面跟了一个逗号和$1。printf将根据控制串中的格式说明来格式化字符串Linux。
实例1
$ echo "Linux" | awk '{printf "|%15s|\n",$1}'
|          Linux|
说明:字符串Linux被打印成一个占15 格、向右对齐的字符串,夹在两个竖杠之间,以换行符结尾。
实例2
$ cat employees
Tom   Jones 4424 5/12/66 543354
Mary  Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '{printf "The name is: %-15s ID is %8d\n",$1,$3}' employees
The name is Tom             ID is 4424
The name is Mary            ID is 5346
The name is Sally           ID is 1654
The name is Billy           ID is 1683
说明:要打印的字符串放置在两个双引号之间。第一个格式说明符是%-15s,它对应的参数是$1,紧挨着控制串的右半边引号后面的那个逗号。百分号引出格式说明:短划线表示左对齐,15s表示占15格的字符串。这条命令用来打印一个左对齐、占15格的字符串,后面跟着字符串的ID和一个整数。
格式:%8d表示在字符串的这个位置打印$2 的十进制(整数)值。这个整数占8格,向右对齐。您也可以选择将加引号的字符串和表达式放在圆括号里。
实例3

 

模式与操作

  • 模式:awk模式用来控制awk对输入的文件执行什么操作。模式由正则表达式、判别真伪的表达式或者二者的结合。awk的默认操作是打印所有使表达式结果为真的文本行。模式表达式中暗含if语句。如果表达式暗含if(如果)的意思,就不必用花括号把它括起来。当if显式的给出时,这个表达式就成了操作语句,语法将不一样。
  • 操作:是大括号中分号分隔的语句。如果操作前有一个模式,则该模式控制执行操作的时间。操作可以是简单的语句或者复杂的语句词。同一行内的多条语句用分号相隔,独占一行的语句则以换行符分隔。

 

格式
1awk '/RE/' filename
2awk 'judge' filename
3awk '{action1;}' filename || awk '{action1;action2;action3;}' filename
4awk '{action1
    action2
    }' filename

说明
1、  模式可以用正则来控制
2、  模式可以用条件表达式来控制
3、  操作是在大括号中的执行内容,用分号隔开。
4、  当多条操作不在同一行时,可以换行进行操作

 

示例
sh-4.2# cat employees 
Tom Jones      4424    5/12/66    543354
Mary Adams     5346    11/4/63    28765
Sally Chang    1654    7/22/54    650000
Billy Black    1683    9/23/44    336500

sh-4.2# awk '/Tom/' employees 
Tom Jones    4424    5/12/66    543354
说明:如果在输入文件中匹配Tom模式内容,则打印Tom模式所在的记录。如果没有显式的指定操作,默认操作是打印文本行,等价于命令;

sh-4.2# awk '$3 < 4000' employees 
Sally Chang    1654    7/22/54    650000
Billy Black    1683    9/23/44    336500
说明:如果第三字段小于4000,则打印该记录,这里用的就是条件表达式。

 

sh-4.2# cat employees 
Tom Jones      4424    5/12/66    543354
Mary Adams     5346    11/4/63    28765
Sally Chang    1654    7/22/54    650000
Billy Black    1683    9/23/44    336500

sh-4.2# awk '{print $1,$2}' employees 
Tom Jones
Mary Adams
Sally Chang
Billy Black
说明:该操作用来打印employees 文件中所有记录的前两个字段。

 

sh-4.2# awk '/Tom/{print "Hello there,"$1}' employees 
Hello there,Tom
说明:如果记录中包含模式Tom,就会打印出Hello there,并且在该内容最后加入记录的第一字段内容。模式可以用正则来进行匹配。

 

正则表达式

  • awk的正则表达式是置于两个斜杠之间的,由字符串组成的模式。
  • awk默认支持扩展正则表达式
  • awk对正则表达式进行条件匹配,如果条件成立,则打印与正则表达式匹配的记录
  • awk不支持基础正则表达式
  • awk的正则表达式表请自行参考其他文档;参考文档:https://www.cnblogs.com/guge-94/p/10678890.html

 

关系运算符

运算符   含义        示例
<       小于        x < y
<=      小于或者等于    x <= y
==      等于        x == y
!=      不等于       x != y
>=      大于等于      x >= y
>       大于        x > y
~       与正则表达式匹配  x ~ /y/
!~      与正则表达式不匹配 x !~ /y/

 

示例
sh-4.2# cat datafile 
northwest    NW    Joel Craig        3.0    .98    3    3
western      WE    Sharon Kelly      5.3    .97    5    23
southwest    SW    Chris Foster      2.7    .8     2    18
southern     SO    May Chin          5.1    .95    4    15
southeast    SE    Derek Johnson     4.0    .7     4    17
eastern      EA    Susan Beal        4.4    .84    5    20
northeast    NE    TJ Nichols        5.1    .94    3    13
north        NO    Val Shultz        4.5    .89    5    9
central      CT    Sheri Watson      5.7    .94    5    13

sh-4.2# awk '$5 ~ /\.[7-9]+/' datafile 
southwest    SW    Chris Foster      2.7    .8     2    18
central      CT    Sheri Watson      5.7    .94    5    13
说明:打印datafile文件中第五字段包含'.'(句点),后面跟一或者多个7~9之间的数字。

sh-4.2# awk '$2 !~ /E/{print $1,$2}' datafile 
northwest NW
southwest SW
southern SO
north NO
central CT
说明:如果某条记录的第二字段不含模式E,则打印该记录的第一字段,隔一个空格在打印第二个字段。

sh-4.2# awk '$3 ~ /^Joel/{print $3" is a nice guy."}' datafile 
Joel is a nice guy.
说明:如果第三个字段以模式Joel开头,则打印该字段,并且在后面跟上字符串"is a nice guy."。注意,如果要打印空格,就要把它包括在字符串中。

sh-4.2# awk '$8 ~ /[0-9][0-9]$/{print $8}' datafile 
23
18
15
17
20
13
13
说明:如果第八个字段以两个数字结尾,则打印该字段。

sh-4.2# awk '$4 ~ /Chin$/{print "The price is $" $8 "."}' datafile 
The price is $15.
说明:如果第四个字段以Chin结尾,则打印双引号间的字符串("The price is $.")、第八个字段和最后那个只有一个句点的字符串。

 

sh-4.2# cat datafile2 
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

sh-4.2# awk '{print $1}' datafile2 
Joel
Sharon
Chris
May
Derek
Susan
TJ
Val
Sheri
说明:默认的输入字段分隔符是空白符。本例按照默认分隔符打印第一字段。

sh-4.2# awk -F'[:]' '{print $1}' datafile2 
Joel Craig
Sharon Kelly
Chris Foster
May Chin
Derek Johnson
Susan Beal
TJ Nichols
Val Shultz
Sheri Watson
说明:以冒号为分隔符,打印第一字段。

sh-4.2# awk -F: '{print "Number of fields:"NF}' datafile2 
Number of fields:7
Number of fields:7
Number of fields:7
Number of fields:7
Number of fields:7
Number of fields:7
Number of fields:7
Number of fields:7
Number of fields:7
说明:因为字段以分隔符被设为冒号,所以每条记录的字段数变成了7.

sh-4.2# awk -F "[ :]" '{print $1,$2}' datafile2 
Joel Craig
Sharon Kelly
Chris Foster
May Chin
Derek Johnson
Susan Beal
TJ Nichols
Val Shultz
Sheri Watson
说明:使用awk命令,可以用正则表达式来指定多个字段分隔符。空格或者冒号将被指定为是字段分隔符。本例中打印头两个字段(中括号必须在引号内,以防shell将他们解释成shell元字符)。

 

脚本训练一

范例1使用了下面的datafile文件。

sh-4.2# cat datafile
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

 

范例1
sh-4.2# cat awkfile 
/^north/{print $1,$2,$3}
/^south/{print "The "$1" district"}

sh-4.2# awk -f awkfile datafile
northwest NW Joel
The southwest district
The southern district
The southeast district
northeast NE TJ
north NO Val

说明:
1、如果记录以模式north开头,则打印头3个字段。
2、如果记录以模式south开头,则依次打印字符串The,第一个字段的值和字符串district。
3、-f选项后面跟的是awk脚本文件名,接下来的那个参数是要处理的输入文件名。

 

范例2

本范例使用如下文件

sh-4.2# cat datafile3 
Mike Harrington:(510)548-1278:250:100:175
Christian Dobbins:(408)538-2358:155:90:201
Susan Dalsass:(206)654-6279:250:60:50
Archie McNichol:(206)548-1348:250:100:175
Jody Savage:(206)548-1278:15:188:150
Guy Quigley:(916)343-6410:250:100:175
Dan Savage:(406)298-7755:450:300:275
Nancy McNeil:(206)548-1278:250:80:75
John Goldenrod:(916)348-4278:250:100:175
Chet Main:(510)548-5258:50:95:135
Tom Savage:(408)926-3456:250:168:200
Elizabeth Stachelin:(916)440-1763:175:75:300

说明:
1、打印所有的电话号码
sh-4.2# awk -F: '{print $2}' datafile3 
(510)548-1278
(408)538-2358
(206)654-6279
(206)548-1348
(206)548-1278
(916)343-6410
(406)298-7755
(206)548-1278
(916)348-4278
(510)548-5258
(408)926-3456
(916)440-1763

2、打印Dan的电话号码
sh-4.2# awk -F: '/Dan/{print $2}' datafile3 
(406)298-7755

3、打印Susan的姓名和电话号码
sh-4.2# awk -F: '/Susan/{print $1,$2}' datafile3
Susan Dalsass (206)654-6279

4、打印所有姓以D开头的姓氏
sh-4.2# awk '$2 ~ /^D/{print $2}' datafile3 | awk -F: '{print $1}'
Dobbins
Dalsass

5、打印所有以C或者E开头的全名
sh-4.2# awk -F: '/^[CE]/{print $1}' datafile3 
Christian Dobbins
Chet Main
Elizabeth Stachelin

6、打印所有只包含4个字符的名
sh-4.2# awk '$1 ~ /[a-zA-Z]{4}/{print $1}' datafile3 
Mike
Christian
Susan
Archie
Jody
Nancy
John
Chet
Elizabeth

7、打印所有区号为916的人的名字
sh-4.2# awk -F: '$2 ~ /\(916\)/{print $1}' datafile3 
Guy Quigley
John Goldenrod
Elizabeth Stachelin

8、打印Mike的资助金额,每一个值要使用美元符开头,例如:"$250"sh-4.2# awk -F: '$1 ~ /Mike/{print "$"$3}' datafile3 
$250

9、打印所有的姓,后面跟一个逗号和名
sh-4.2# awk -F: '{print $1}' datafile3 | awk '{print $2",",$1}'
Harrington, Mike
Dobbins, Christian
Dalsass, Susan
McNichol, Archie
Savage, Jody
Quigley, Guy
Savage, Dan
McNeil, Nancy
Goldenrod, John
Main, Chet
Savage, Tom
Stachelin, Elizabeth

10、编写一个名为facts的脚本完成下列工作
a、打印所有姓氏为Savage的人的全名和电话号码
b、打印Chet的资助金额
c、打印所有在第一个月资助了250美元的人
sh-4.2# cat facts 
$1 ~ /(Savage)$/{print $1,$2}
$1 ~ /^Chet/{print $3}
$3 ~ /250/{print $1}

sh-4.2# awk -F: -f facts datafile3 
Mike Harrington
Susan Dalsass
Archie McNichol
Jody Savage (206)548-1278
Guy Quigley
Dan Savage (406)298-7755
Nancy McNeil
John Goldenrod
50
Tom Savage (408)926-3456
Tom Savage

 

条件语句

三目运算格式

{(条件表达式1)?表达式2:表达式3;action}

 

if/else条件语句格式

if (expression1)
expression2
else
expression3
fi

或者

if(expression1){expression2}else{expression3}

 

三目运算示例
sh-4.2# cat test 
1 2 3

sh-4.2# awk '{max=$1<$2?max=10:max=20;print max}' test 
10
或者
sh-4.2# awk '{max=($1<$2)?max=10:max=20;print max}' test 
10
说明:条件表达式1($1<$2)可以用小括号括起来,也可以不括;条件表达式1之后接表达式2,表达式2是赋值;当条件表达式1成立则执行表达式2的内容,否则执行冒号后面的表达式3的内容,这里的赋值与判断需要结合awk的比较表达式。

 

if/else示例
sh-4.2# awk '{if($1<$2){print 10}else{print 20}}' test 
10

或者
sh-4.2# awk '{if($1<$2)
print 10
else print 20}' test
10

需要注意的是,三目运算在打印条件表达式里面的内容时候,变量不需要加美元符,即$。

 

复合模式

  • 复合模式就是将两个表达式进行逻辑运算
  • 逻辑操作符用来测试表达式或者模式的真假。
  • 逻辑操作符&&表示逻辑与,当所有表达式都成立时,模式为真;有一个表达式不成立,模式都为假。
  • 逻辑操作符||表示逻辑或,当所有表达式中有一个成立时,模式为真;当所有表达式全部不成立,模式为假。
  • 逻辑操作符!表示逻辑非,表示当表达式不成立时,模式为真。
示例
sh-4.2# cat test 
1 2 3
2 1 0
sh-4.2# awk '$1 > 0 && $3 == 0' test 
2 1 0
说明:awk会打印同时满足$1大于0及$3等于0的行,其他行并不会打印

sh-4.2# awk '$2 == 2 || $1 > 1' test 
1 2 3
2 1 0
说明:awk会打印$2等于2或者$1大于1的行;第一记录中的第二字段值为2,所以可以被打印;第二记录中的第一字段大于1所以也能被打印。

sh-4.2# cat datafile 
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13
sh-4.2# awk '!($8 == 13){print $8}' datafile 
3
23
18
15
17
20
9
说明:如果第八个字段等于13,!就对表达式取反,并取消打印操作。!是一元否定操作符。

 

定位范围

概述:范围模式先匹配从一第一个模式的首次出现到第二个模式的首次出现之间的内容,然后匹配从第一个模式的下一次出现到第二个的下一次出现,以此类推。如果匹配到第一个模式而没有发现第二个模式,awk就将显示从第一个模式首次出现的行到文件末尾之间的所有行。

 

示例
awk '/Tom/,/Suzanne/' filename

说明
awk将显示从Tom首次出现的行到Suzanne首次出现的行的这个范围内的所有行,包括这两个边界在内。如果没有找到Suzanne,awk将继续打印各行直至文件末尾。如果打印完Tom到Suzanne的内容之后,又出现了Tom,awk就又一次开始显示行,直到找到下一个Suzanne或文件末尾。

 

小结测试

1、通过awk验证/etc/passwd文件的合法性

sh-4.2# cat /etc/passwd | awk -F: 'NF != 7{printf ("line %d,does not have 7 fields: %s\n",NR,$0)} \
 $1 !~ /[A-Za-z0-9]/{printf("line %d,nonalphanumeric user id: %s\n",NR,$0)} \
 $2 == "*" {printf("line %d,no password:%s\n",NR,$0)}'
说明:
a、  显示/etc/passwd文件,并且通过管道将内容输入到awk程序
b、  awk通过选项-F改变FS变量的默认值
c、  判断字段数不等于7的行,则执行接下来的操作块,printf函数打印字符串"line %d,does not have 7 fields: %s\n",NR,$0"
d、  判断第一字段不是大小写字母或者数字开头的内容,printf函数打印字符串"line %d,nonalphanumeric user id: %s\n",NR,$0"
e、  判断第二字段是否是一个星号,如果是就打印"line %d,no password:%s\n",NR,$0"内容

 

下面所有例子请使用该文件内容:

sh-4.2# cat datefile 
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

 

2、如果第七字段等于5,就打印该记录

sh-4.2# awk '$7 == 5{print $0}' datefile 
western     WE  Sharon Kelly    5.3 .97 5   23
eastern     EA  Susan Beal      4.4 .84 5   20
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

 

3、如果第二字段等于字符串"CT",就打印它的第一字段和第二字段,字符串必须加引号。

sh-4.2# awk '$2 == "CT"{print}' datefile 
central     CT  Sheri Watson    5.7 .94 5   13

 

4、如果记录的第七字段不等于数字5,就打印该记录

sh-4.2# awk '$7 != 5{print $0}' datefile 
northwest   NW  Joel Craig      3.0 .98 3   4
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
northeast   NE  TJ Nichols      5.1 .94 3   13

 

5、如果记录的第七个字段值小于5,就打印他的第四和第七字段

sh-4.2# awk '$7 < 5{print $4,$7}' datefile 
Craig 3
Foster 2
Chin 4
Johnson 4
Nichols 3

 

6、如果记录的第六字段大于.9,就打印他的第一字段和第六字段

sh-4.2# awk '$6 > .9{print $1,$6}' datefile 
northwest .98
western .97
southern .95
northeast .94
central .94

 

7、如果记录的第八字段小于等于17,就打印该记录

sh-4.2# awk '$8 <= 17{print $0}'  datefile 
northwest   NW  Joel Craig      3.0 .98 3   4
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

 

8、如果记录的第八字段大于或者等于17,则打印该字段

sh-4.2# awk '$8 >= 17{print $8}' datafile 
23
18
17
20

 

9、如果第八个字段等于13,就对表达式取反,并取消打印操作。

sh-4.2# awk '!($8 == 13){print $0}' datafile 
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
north       NO  Val Shultz      4.5 .89 5   9

 

10、如果记录中包含正则表达式southern,就将第五字段的值与10相加,并打印结果。注意,以浮点数形式表示。

sh-4.2# awk '/southern/{print $5 + 10}' datafile 
15.1

 

11、如果记录中包含正则表达式southern,就将第八字段的值和10相加,并打印结果。

sh-4.2# awk '/southern/{print $8 + 10}' datafile 
25

 

12、如果记录中包含正则表达式southern,就将10.56与第五字段的值相加并显示计算结果。

sh-4.2# awk '/southern/{print $5 + 10.56}' datafile 
15.66

 

13、如果记录中包含了正则表达式southern,就用第八个字段减去10,并显示计算结果。

sh-4.2# awk '/southern/{print $8 - 10}' datafile 
5

 

14、如果记录中包含了正则表达式southern,就用第八个字段除以2,并显示计算结果。

sh-4.2# awk '/southern/{print $8 / 2}' datafile 
7.5

 

15、如果记录中包含了正则表达式northeast,就用第八个字段除以3,并显示计算结果。

sh-4.2# awk '/northeast/{print $8 / 3}' datafile 
4.33333

 

16、如果记录中包含了正则表达式southern,就用第八个字段乘以2,并显示计算结果。

sh-4.2# awk '/southern/{print $8 * 2}' datafile 
30

 

17、如果记录中包含了正则表达式northeast,就用第八个字段对3进行取余,并显示计算结果。

sh-4.2# awk '/northeast/{print $8 % 3}' datafile
1

 

18、如果记录中第三字段以Susan开头的内容,就打印Percentage:加第六字段内容,并且对第六字段加.2,接着输出Volume:,并且在后面加入第八字段的内容

sh-4.2# awk '$3 ~ /^Susan/{print "Percentage:"$6 + .2 "Volume: "$8}' datafile 
Percentage:1.04Volume: 20

 

19、从正则表达式western开始的记录,到正则表达式eastern开始的记录,这个范围内的所有记录都打印出来。如果之后又发现western开始的记录,则再次打印记录至eastern结束的记录或者文件的末尾行。

sh-4.2# awk '/western/,/eastern/{print $0}' datafile 
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20

 

20、如果记录中的第七字段大于4,print函数就输出问号后面的这个表达式,否则print函数输出的就是冒号后面的那个表达式。

sh-4.2# awk '{print ($7 > 4?"high "$7 : "low "$7)}' datafile 
low 3
high 5
low 2
low 4
low 4
high 5
low 3
high 5
high 5

 

21、如果记录的第三个字段等于字符串"Chris",操作就将"Christian"赋给第三个字段,然后显示该记录。双引号检查它的操作数是否相等,而单引号则用于赋值。

sh-4.2# awk '$3 == "Chris"{$3 = "Christian";print}' datafile 
southwest SW Christian Foster 2.7 .8 2 18

 

22、如果找到正则表达式Derek,就将第八个字段加上12,并将结果赋值给第八字段并显示。这个运算的另一种方式是:$8=$8 + 12

sh-4.2# awk '/Derek/{$8 +=12;print}' datafile 
southeast SE Derek Johnson 4.0 .7 4 29

 

23、对每一条记录执行如下操作:将第七个字段除以三,然后将余数赋给第七字段,并显示。

sh-4.2# awk '{$7%=3;print $7}' datafile 
0
2
2
1
1
2
0
2
2

 

24、练习题

根据datafile文件内容进行操作:

sh-4.2# cat datafile 
Mike Harrington:(510)548-1278:250:100:175
Christian Dobbins:(408)538-2358:155:90:201
Susan Dalsass:(206)654-6279:250:60:50
Archie McNichol:(206)548-1348:250:100:175
Jody Savage:(206)548-1278:15:188:150
Guy Quigley:(916)343-6410:250:100:175
Dan Savage:(406)298-7755:450:300:275
Nancy McNeil:(206)548-1278:250:80:75
John Goldenrod:(916)348-4278:250:100:175
Chet Main:(510)548-5258:50:95:135
Tom Savage:(408)926-3456:250:168:200
Elizabeth Stachelin:(916)440-1763:175:75:300

 

1、打印在第二个月捐款超过100美元的人的姓和名

sh-4.2# awk -F: '$4 > 100{print $1}' datafile 
Jody Savage
Dan Savage
Tom Savage

 

2、打印在最后一个月捐款少于85美元的人的姓名和电话号码

sh-4.2# awk -F: '$5 < 85{print $1,$2}' datafile 
Susan Dalsass (206)654-6279
Nancy McNeil (206)548-1278

 

3、打印第二个月捐款额在75~150美元之间的人

sh-4.2# awk -F: '$4 > 75 && $4 < 150 {print $1}' datafile 
Mike Harrington
Christian Dobbins
Archie McNichol
Guy Quigley
Nancy McNeil
John Goldenrod
Chet Main

 

4、打印这三个月的捐款总额不超过800美元的人

sh-4.2# awk -F: '{num=$3+$4+$5;if(num<800){print $1}}' datafile
Mike Harrington
Christian Dobbins
Susan Dalsass
Archie McNichol
Jody Savage
Guy Quigley
Nancy McNeil
John Goldenrod
Chet Main
Tom Savage
Elizabeth Stachelin

 

5、打印月均捐款额大于200美元的人的姓名和电话号码

sh-4.2# awk -F: '{num=$3+$4+$5;ave=num/3;if(ave>200){print $1,$2}}' datafile 
Dan Savage (406)298-7755
Tom Savage (408)926-3456

 

6、打印不在916区的人的姓

sh-4.2# awk -F: '$2 !~ /\(916\)/{print $1}' datafile 
Mike Harrington
Christian Dobbins
Susan Dalsass
Archie McNichol
Jody Savage
Dan Savage
Nancy McNeil
Chet Main
Tom Savage

 

7、打印每条记录,并在记录前加上其记录号

sh-4.2# awk '{print NR,$0}' datafile 
1 Mike Harrington:(510)548-1278:250:100:175
2 Christian Dobbins:(408)538-2358:155:90:201
3 Susan Dalsass:(206)654-6279:250:60:50
4 Archie McNichol:(206)548-1348:250:100:175
5 Jody Savage:(206)548-1278:15:188:150
6 Guy Quigley:(916)343-6410:250:100:175
7 Dan Savage:(406)298-7755:450:300:275
8 Nancy McNeil:(206)548-1278:250:80:75
9 John Goldenrod:(916)348-4278:250:100:175
10 Chet Main:(510)548-5258:50:95:135
11 Tom Savage:(408)926-3456:250:168:200
12 Elizabeth Stachelin:(916)440-1763:175:75:300

 

8、打印每个人的姓名和捐款总额

sh-4.2# awk -F: '{num=$3+$4+$5;print $1,num}' datafile 
Mike Harrington 525
Christian Dobbins 446
Susan Dalsass 360
Archie McNichol 525
Jody Savage 353
Guy Quigley 525
Dan Savage 1025
Nancy McNeil 405
John Goldenrod 525
Chet Main 280
Tom Savage 618
Elizabeth Stachelin 550

 

9、把Chet第二个月的捐款额加上10

sh-4.2# awk -F: '/Chet/{$4=$4+10;print $4}' datafile 
105

 

10、把Nancy McNeil的名字改成Louise Mclnnes。

sh-4.2# awk -F: '/(Nancy McNeil)/{$1="Louise Mclnnes";print $0}' datafile 
Louise Mclnnes (206)548-1278 250 80 75
或者
sh-4.2# sed -n 's/Nancy McNeil/Louise Mclnnes/p' datafile 
Louise Mclnnes:(206)548-1278:250:80:75

 

变量

  • awk变量跟linux变量类似,分为内置变量与自定义变量;
  • awk自定义变量根据变量内容可以分为:数值变量和字符串变量
  • awk程序中提到的内容,变量就开始存在。变量可以是一个字符串,或者数字,也可以是两者的结合。
  • awk变量被设置之后,就变成与右边那个表达式相同的类型或者值。
  • awk自定义变量在打印的时候不需要在前面美元符。
  • awk未经初始化的变量值只能是0或者"",究竟是哪个则取决于它们被使用时的上下文。name = "Nancy" name是字符串变量,Nancy是字符串变量内容,Nancy一定要用双引号引起来,否则name被视为是空串;x++ x是数字,它的初始化为0,然后+1;number = 35 number是数字,数字可以不用双引号
将字符串转变成数字
name + 0

 

将数字转化成字符串
number ""

注意:所有由split函数创建的字段或者数组元素都被视为字符串,除非它们只包含数字值。如果某个字段或者数组元素为空,它的值就是空串。空行也可以被视为空串。

 

  用户自定义变量的变量名可以由字母、数字的下划线组成,但是不能以数字开头。awk的变量不用声明其类型,awk可以从变量在表达式中的上下文推导出它的数据类型。如果变量未被初始化,awk会将字符串初始化成空串,将数值变量初始化成0。必要时,awk会将字符型变量转化成数值型变量,或者反向转换。下面是对变量赋值的运算符:

运算符     含义  等效表达
=         a=5   a=5
+=        a=a+5 a+=5
-=        a=a-5 a-=5
*=        a=a*5 a*=5
/=        a=a/5 a/=5
%=        a=a%5 a%=5
^=        a=a^5 a^=5

 

最简单的赋值方式是求出表达式的效果,然后将其赋值给变量。格式如下:

变量=表达式

 

范例1

awk '$1 ~ /Tom/{wage=$2*$3;print wage}' filename

说明:awk将扫描第一个字段中包含Tom的行。如果发现某一行符合条件,就将其第二字段与第三字段相乘,并且将结果赋值给wage。由于乘法是算数运算,所以awk会将wage的默认值初始化为0.

 

  递增和递减运算符:如果要将操作数加1,可以使用递增运算符。表达式x++,等价于x=x+1。类似的,递减运算符则使操作数减少1。表达式x--等价于x=x-1。当进行循环操作时,如果只需要递增或者递减一个计数器,这种运算符就很有用了。递增和递减运算符可以放在操作数的前面,如++x;也可以置于操作数后面,如x++。用于赋值语句时,这两个运算符的位置不同可能会造成运算结果的差异。

 

范例2

x=1;y=x++;print x,y}
说明:上面这个例子中的++称为后递增运算符,y被先赋值1,然后x才+1,这样当运算做完之后,y=1,而x=2.

 

范例3

{x=1;y=++x;print x,y}
说明:上面这个例子中的++称为先递增运算符,先将x+1,然后才将值2赋给y,这样当运算做完之后,y=2,而x=1.

命令行上的用户自定义变量:可以在命令行上对变量赋值,然后将其传递给awk脚本。下面对awk内置变量进行描述

 

常用内置变量

常用内置变量名  含义
FILENAME    当前输入文件的文件名
FNR       当前文件的记录数
FS        输入字段分隔符,默认为空格
OFS       输出字段分隔符
RS        输入记录的分隔符
ORS       输出记录分隔符
NF        当前记录中的字段数
NR       目前的记录数
OFMT      数字的输出格式
IGNORECASE  IGNORECASE为非0值,则在正则表达式和字符串匹配中不区分大小写

 

不常用内置变量

ARGC   命令行参数的目录
ARGIND  命令行中当前文件在ARGV内的索引(仅用于gawk)
ARGV   命令行参数构成的数组
CONVFMT  数字转换格式,默认为%.6g(仅用于gawk)
ENVIRON 包含当前shell环境变量值的数组
ERRNO   UNIX系统错误消息
FIELDWIDTHS  输入字段宽度的空白分隔字符串
RLENGTH    match函数匹配到字符串的长度
RT       记录终结符,对于匹配字符或者用RS指定的regex,gawk将RT设置到输入文本
SUBSEP     数组下标分隔符
RSTART     match函数匹配到的字符串的偏移量

 

字段变量:字段变量可以像用户定义的变量一样使用,唯一的区别是它们引用了字段。新的字段可以通过赋值来创建。字段变量引用的字段如果没有值,则被赋值为空串。字段的值发生变化时,awk会以OFS的值作为字段分隔符重新计算$0变量的值。字段数目通常被限制在100以内。

 

范例4

awk '{$5=1000*$3/$2;print}' filename
说明:如果不存在第五字段,awk将创建它并将表达式1000*$3/$2的结果赋给它。如果存在第五字段,就直接将表达式的结果赋值给它,覆盖该字段原来的内容。

 

范例5

awk '$4 == "CA"{$4 == "Californin";print}' filename
说明:如果第四字段匹配字符串CA,awk就将其重新赋值为California。双引号是必需的,如果没有这对双引号,字符串CA就会被当成一个初始值为空的用户自定义变量。

 

范例6

sh-4.2# cat employees2 
Tom Jones:4424:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Billy Black:1683:9/23/44:336500

sh-4.2# awk -F: '$1 == "Mary Adams"{print NR,$1,$2,$NF}' employees2 
2 Mary Adams 5346 28765
说明:-F选项把字段分隔符设置为冒号。print函数一次打印出记录号、第一字段、第二字段、最后一个字段($NF表示最后一个字段)。

 

范例7

sh-4.2# cat employees2 
Tom Jones:4424:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Billy Black:1683:9/23/44:336500

sh-4.2# awk -F: '{IGNORECASE=1};$1 == "mary adams" {print NR,$1,$2,$NF}' employees2
2 Mary Adams 5346 28765
说明:-F选项把字段分隔符设置为冒号。若awk的内置变量IGNORECASE为非0值,则在正则表达式和字符串匹配中不区分大小写。接着,字符串mary adams匹配。最后,print函数一次打印出记录号、第一字段、第二字段和最后一个字段。

 

BEGIN模式

  BEGIN模式后面跟了一个操作块。awk必须在对输入文件进行任何处理之前先执行该操作块。实际上,不需要任何输入文件,也能对BEGIN块进行测试,因为awk要执行完BEGIN操作块后才开始读取输入。BEGIN操作常常被用于修改内部变量(OFS、RS、FS等等)的值、为用户自定义变量赋值和打印输出的页眉或者标题。命令格式如下

awk 'BEGIN{action}' filename

 

示例

awk 'BEGIN{FS=":";OFS="\t";ORS="\n\n"}{print $1,$2,$3}' filename

说明
在处理输入文件之前,awk先把字段分隔符设为冒号,把输出分隔符设为制表符,还把输出记录分隔符设为两个换行符。如果BEGIN的操作块中有两条或者两条以上语句,必须用分号分隔它们或每行只写一条语句。

awk 'BEGIN{print "MAKE YEAR"}'
MAKE YEAR

说明
awk将显示MAKE YEAR。awk打开输入文件之前先执行BEGIN之后的操作块,即使没有指定输入文件,awk也照样打印MAKE YEAR。调试awk脚本时,可以先测试好BEGIN块的操作,再编写程序的其余部分。

 

END模式

  END模式不匹配任何输入行,而是执行任何与之关联的操作。awk处理完所有输入行之后才处理END模式内容。命令格式如下

awk 'END{action}' filename

 

示例

awk 'END{print "The number of records is" NR}' filename
The number of records is 4

说明
awk处理完整个文件后才开始执行END块。此时NR值是最后这条记录的记录号。

sh-4.2# awk '/Mary/{count++}END{print "Mary was found " count " times."}' employees
Mary was found 1 times.

说明
每遇到一个包含Mary模式的行,用户自定义变量count的值就加1。awk处理完整个文件后,END块打印字符串Mary was found,后面再跟上count的值和字符串times。

 

重定向

  输出重定向:将awk的输出重定向到文件时,会用到shell的重定向操作符。重定向的目标文件必须用双引号引起来。如果使用的重定向操作符为> ,则文件被打开并且被清空。文件一旦被打开,就会保持打开状态,知道awk程序的退出。此后print语句的输出都将追加到文件尾部。符号>>也用于打开文件,但是是附加,并不是覆盖,它只向文件尾部追加内容。

 

示例

范例1
awk '$4 >= 70{print $1,$2 > "passing_file"}' filename

说明:如果记录的第四个字段的值大于或者等于70,就将它的第一字段和第二字段的值到覆盖passing_file中。

输入重定向:函数getline用于从标准输入或者管道或者文件读取输入。getline函数用于读取下一输入行,并且设置内置变量NF、NR和FNR。如果读到一条记录,函数就返回1,如果读到EOF,则返回0。如果发生错误,比如打开文件失败,则getline函数返回-1范例2
sh-4.2# awk 'BEGIN{"date"|getline d;print d}' filename
Tue Nov 27 04:32:18 EST 2018

说明:先执行linux的date命令,将输出通过管道发给getline,再通过getline将传来的内容赋值给用户自定义的变量d,然后打印d。

范例3
sh-4.2# awk 'BEGIN{"date"|getline d;split (d,mon);print mon[2]}' filename
Nov

说明:先执行date命令,将输出内容通过管道发送给getline,接着getline从管道读取输入,然后保存在用户自定义变量d中。split函数从d中生成一个名为mon的数组。最后程序打印出数组mon的第二个元素。

范例4
sh-4.2# awk 'BEGIN{while("ls"|getline)print}'
employees
employees2
说明:ls命令的输出将传递给getline;每次循环一次,getline就从ls的输出中读取一行,并将显示到屏幕。不需要输出文件,因为awk会先执行完BEGIN后面的操作块,才读取文件。

范例5
awk 'BEGIN{printf "What is your name?";getline name < "/dev/tty"}\
$1 ~ name{print "Found "name" on line ",NR" ."}END{print "See ya, "name" ."}' filename

说明
1、在屏幕上输出What is your name?,然后等待用户相应,getline函数将从终端接收输入,直到用户换行,然后将输入保存在用户自定义的变量name中。
2、如果第一个字段匹配之前赋给name的值,则执行print函数。
3、END语句打印出"See ya,",然后显示Eiile(保存在变量name中的值),再跟上一个句号。

范例6
sh-4.2# awk 'BEGIN{while(getline < "/etc/passwd" > 0)lc++;print lc}' file
30
说明:awk将逐行读取文件/etc/passwd,lc随之递增至EOF(最后一行),然后打印lc的值,及文件passwd的行数。如果文件不存在,getline的值将会是-1。如果读到文件尾部,返回值是0,而读到一行时,则返回值是1.因此命令"while (getline<"/etc/junk")"遇到/etc/junk文件不存在时,会进入死循环,因为返回值-1导致条件为真。

 

管道

  如果在awk程序中打开了管道,就必须先关闭它才能打开另一个管道。管道右边的命令包括被括在双引号之间。每次只能打开一个管道。

 

示例
例1
sh-4.2# cat names 
john smith
alice cheba
george goldberg
susan goldberg
tony tram
barbara nguyen
elizabeth lone
dan savage
eliza goldberg
john goldenrod

sh-4.2# awk '{print $1,$2| "sort -r +1 -2 +0 -1"}' names 
tony tram
john smith
dan savage
barbara nguyen
elizabeth lone
john goldenrod
susan goldberg
george goldberg
eliza goldberg
alice cheba

说明:awk使用管道将print语句输出的内容发送给sort命令作为输入。sort命令将以第二个字段为主键、第一个字段为次键对输入进行逆排序。这种情况下,sort命令必须被双引号引起来。
如果打算再次在awk程序中使用某个文件或者管道进行读写,则可能要先关闭程序,因为其中的管道会保持打开状态直到脚本运行结束。注意,管道一旦被打开,就会保持打开状态直到awk退出。因此END块中的语句也受到管道的影响。下面这个例子中,END块的第一行命令将用来关闭管道。

示例2
{print $1,$2,$3|"sort -r +1 -2 +0 -1"}END{close ("sort -r +1 -2 +0 -1")}

说明
1、awk把输入文件的每一行记录都通过管道发送给sort命令
2、执行到END块时,管道被关闭。双引号中的字符串必须是最初打开管道的pipe命令字符串完全一致。

 

system函数:awk的内置函数system以unix/linux的系统命令作为参数,执行该命令并且将命令的退出状态返回给awk程序。它很像C语言的一个标准库函数,该函数恰巧也叫system()。注意,作为参数的系统命令必须加双引号。格式如下:

system("command")

 

示例

{
system("cat"$1)
system("clear")
}

说明
1、system函数以unix/linux的cat命令和输入文件的第一个字段作为参数。cat命令把第一个字段的值,即一个文件名,作为参数。unix/linux shell可以执行cat命令。
2、system函数以unix/linux的clear命令作为参数。shell将执行clear命令,清空屏幕。

 

小结测试二

本节范例,除特别声明外,都使用下面这个重复出现过多次的datafile文件。

sh-4.2# cat datafile 
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

 

范例1:如果记录以正则表达式north开头,则创建用户自定义的变量count,然后加1并打印它的值。

sh-4.2#  awk '/^north/{count += 1;print count}' datafile
1
2
3

 

范例2:如果记录以正则表达式north开头,则自动递增符将用户自定义变量count加1。然后count的值被打印出来。

sh-4.2# awk '/^north/{count++;print count}' datafile
1
2
3

 

范例3:第七个字段的值被赋给用户自定义变量x后,自动递减运算符将第七个字段减1。该命令会打印出x以及第七个字段的值。

sh-4.2# awk '{x=$7--;print x,$7}' datafile
3 2
5 4
2 1
4 3
4 3
5 4
3 2
5 4
5 4

 

范例4:如果记录以正则表达式north开头,则打印字符串"The record number is "和NR的值。

sh-4.2# awk '/^north/{print "The record number is "NR}' datafile
The record number is 1
The record number is 7
The record number is 8

 

范例5:打印NR的值和$0的值

sh-4.2# awk '{print NR,$0}' datafile
1 northwest NW  Joel Craig      3.0 .98 3   4
2 western   WE  Sharon Kelly    5.3 .97 5   23
3 southwest SW  Chris Foster    2.7 .8  2   18
4 southern  SO  May Chin        5.1 .95 4   15
5 southeast SE  Derek Johnson   4.0 .7  4   17
6 eastern   EA  Susan Beal      4.4 .84 5   20
7 northeast NE  TJ Nichols      5.1 .94 3   13
8 north     NO  Val Shultz      4.5 .89 5   9
9 central   CT  Sheri Watson    5.7 .94 5   13

 

范例6:如果NR的值在2和5之间,则打印记录号和记录本身。

sh-4.2# awk 'NR==2,NR==5{print NR,$0}' datafile
2 western   WE  Sharon Kelly    5.3 .97 5   23
3 southwest SW  Chris Foster    2.7 .8  2   18
4 southern  SO  May Chin        5.1 .95 4   15
5 southeast SE  Derek Johnson   4.0 .7  4   17

 

范例7:如果正则表达式以north开头,则打印该记录的记录号,后面跟第一字段、第二字段、最后一个字段的值和RS的值。由于print函数默认了一个换行,而RS又加了一个换行,所以记录的间距会加倍。

sh-4.2# awk '/^north/{print NR,$1,$2,$NF,RS}' datafile
1 northwest NW 4 

7 northeast NE 13 

8 north NO 9 

 

范例8:命令行中的字段分隔符被-F选项设置为冒号。如果其记录号为5,则打印该记录的字段的字段数。

sh-4.2# cat datafile2 
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

sh-4.2# awk -F":" 'NR==5{print NF}' datafile2 
7

 

范例9:设置print函数的输出格式变量OFMT为:浮点数精确到小数点后两位,然后用新设置的格式打印1.2456789和12E-2这两个数。

sh-4.2# cat datafile2 
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

sh-4.2# awk 'BEGIN{OFMT="%.2f";print 1.2456789,12E-2}' datafile2 
1.25 0.12

 

范例10:第六个字段和第七个字段的乘积被保存到新的字段$9里面,然后打印出来,命令执行之前有八个字段。之后则应该有9个。

sh-4.2# cat datafile
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

sh-4.2# awk '{$9=$6*$7;print $9}' datafile
2.94
4.85
1.6
3.8
2.8
4.2
2.82
4.45
4.7

 

范例11:每条记录的第十个字段都被赋值为100,这是一个新字段。第九个字段不存在,因而被认为是空字段。输出结果是打印记录的字段数,后面跟$9的值和整条记录。第十个字段的值是100。

sh-4.2# cat datafile
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

sh-4.2# awk '{$10=100;print $0}' datafile
northwest NW Joel Craig 3.0 .98 3 4  100
western WE Sharon Kelly 5.3 .97 5 23  100
southwest SW Chris Foster 2.7 .8 2 18  100
southern SO May Chin 5.1 .95 4 15  100
southeast SE Derek Johnson 4.0 .7 4 17  100
eastern EA Susan Beal 4.4 .84 5 20  100
northeast NE TJ Nichols 5.1 .94 3 13  100
north NO Val Shultz 4.5 .89 5 9  100
central CT Sheri Watson 5.7 .94 5 13  100

 

范例12:BEGIN模式带有一个操作块,其操作是在打开输入文件之前打印字符串"-----------EMPLOYEES-----------"。注意,本例的命令行并未提供输入文件,但并不影响awk命令的执行,因为awk会先执行BEGIN后面的操作块,而不是查找输入文件。

sh-4.2# awk 'BEGIN{print "-----------EMPLOYEES-----------"}' 
-----------EMPLOYEES-----------

 

范例13:BEGIN操作块最先被执行,于是打出标题"-----------EMPLOYEES-----------"。第二个操作块打印输入文件中的每一条记录。当命令需要换行时,可以用反斜杠来取消回车,且在分号或者花括号处进行。

sh-4.2# cat datafile
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

sh-4.2# awk 'BEGIN{print "-----------EMPLOYEES-----------"}{print $0}'  datafile
-----------EMPLOYEES-----------
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

 

范例14:BEGIN操作块被用来初始化变量。变量FS被设为冒号,变量OFS被设为制表符。处理完BEGIN操作块中的内容后,awk就打开datafile2文件并从中读取记录。如果某条记录以正则表达式Sharon开头,则打印它的第一字段、第二字段、第八字段,输出结果的字符以制表符分隔。

sh-4.2# cat datafile2 
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

sh-4.2# awk 'BEGIN{FS=":";OFS="\t";}/^Sharon/{print $1,$2,$8}'  datafile2 
Sharon Kelly    western

 

范例15:awk处理完输入文件后,就开始执行END后的操作快;打印字符串"The total number of record is ",后面跟上NR的值,即最后一行记录的记录号。

sh-4.2# cat datafile
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
north       NO  Val Shultz      4.5 .89 5   9

sh-4.2# awk 'END{print "The total number of record is "NR}' datafile
The total number of record is 9

 

范例16:如果记录以正则表达式north开头,用户自定义变量count就+1。awk处理完输入文件后,打印变量count的值。

sh-4.2# cat datafile
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
north       NO  Val Shultz      4.5 .89 5   9

sh-4.2# awk '/^north/{count +=1}END{print count}' datafile
3

 

范例17
1、BEGIN的操作块最先被执行;设置字段分隔符为冒号,并且打印输出的表头
2、awk脚本的正文部分包含的语句对来自输入文件的每一行都要执行一遍操作
3、END操作块中的语句是在输入文件关闭之后,即awk退出之前执行。
4、awk程序是在命令行上执行的。-f选项后面跟awk脚本文件的名字,再往后则是输入文件名字。

sh-4.2# cat datafile2 
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

sh-4.2# awk 'BEGIN{FS=":";print "  NAME\t\tDISTRICT\tQUANTITY";print "-----------------------------"}{print $1"\t  "$3 "\t\t"$7}{total+=$7}
/north/{count++}END{print "-----------------------------";print "The total quantity is "total;print "The number of northern salespersons is "count "."}' datafile2 
  NAME      DISTRICT    QUANTITY
-----------------------------
Joel Craig    NW        4
Sharon Kelly  WE        23
Chris Foster  SW        18
May Chin      SO        15
Derek Johnson SE        17
Susan Beal    EA        20
TJ Nichols    NE        13
Val Shultz    NO        9
Sheri Watson  CT        13
-----------------------------
The total quantity is 132
The number of northern salespersons is 3.

或者
sh-4.2# cat awk.sc2 
BEGIN{FS=":"
print "  NAME\t\tDISTRICT\tQUANTITY"
print "-----------------------------"}
{print $1"\t  "$3 "\t\t"$7}
{total+=$7}
/north/{count++}
END{print "-----------------------------"
print "The total quantity is "total
print "The number of northern salespersons is "count "."}
sh-4.2# awk -f awk.sc2 datafile2 
  NAME      DISTRICT    QUANTITY
-----------------------------
Joel Craig    NW        4
Sharon Kelly  WE        23
Chris Foster  SW        18
May Chin      SO        15
Derek Johnson SE        17
Susan Beal    EA        20
TJ Nichols    NE        13
Val Shultz    NO        9
Sheri Watson  CT        13
-----------------------------
The total quantity is 132
The number of northern salespersons is 3.

 

范例18:下面的三个范例都使用如下文本内容

sh-4.2# cat datafile
northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

printf函数将浮点数的格式设置为:右对齐,总长度为6,其中小数点占1位,小数点后面占两位,第六个字段的值将四舍五入之后被打印。
sh-4.2# awk '{printf "$%6.2f\n",$6}' datafile
$  0.98
$  0.97
$  0.80
$  0.95
$  0.70
$  0.84
$  0.94
$  0.89
$  0.94

 

范例19:打印一个左对齐、长度为15的字符串。第四个字段被打印在两个竖杠之间,竖杠用来标明打印的宽度。

sh-4.2# awk '{printf "|%-15s|\n",$4}' datafile
|Craig          |
|Kelly          |
|Foster         |
|Chin           |
|Johnson        |
|Beal           |
|Nichols        |
|Shultz         |
|Watson         |

 

范例20:如果记录中包含正则表达式north,则将其第一个字段、第三个字段、第四个字段打印到输出文件districts中。文件被打开后,就保持打开状态直至awk程序退出。文件名districts必须加双引号。

sh-4.2# awk '/north/{print $1,$3,$4 > "districts"}' datafile
sh-4.2# cat districts 
northwest Joel Craig
northeast TJ Nichols
north Val Shultz

 

范例21
1、专用模式BEGIN后跟一个操作块,该操作块中的语句最先被执行,且在awk处理输入文件之前
2、printf函数把字符串NAME显示一个长度为22、左对齐的字符串,跟在后面的字符串DISTRICTS则是右对齐。
3、BEGIN操作块结束。
4、现在awk开始逐行处理输入文件。如果在记录中匹配模式west,则执行这个操作块,即用户自定义的变量count加1。awk在第一次遇到变量count时将先创建它,并赋给它初始值0。
5、printf函数用于输出格式化并发送给管道。所有输出集齐后,被一同发送给sort命令。
6、END操作块起始位置。
7、必须用与打开时完全相同的命令来关闭管道,本例中所使用的命令是"sort"。否则,END块中的语句将与前面的输出一起被排序。

sh-4.2# cat awk.sc3 
BEGIN{
    printf " %-22s%s\n","NAME","DISTRICT"
    print "-----------------------------------------"
}

/west/{count++}
{printf "%s %s\t\t%-15s\n",$3,$4,$1|"sort"}
END{
    close ("sort")
    printf "The number of sales persons in the western "
    printf "region is "count ".\n"
}
sh-4.2# awk -f awk.sc3 datafile
 NAME                  DISTRICT
-----------------------------------------
Chris Foster    southwest      
Derek Johnson   southeast      
Joel Craig      northwest      
May Chin        southern       
Sharon Kelly    western        
Sheri Watson    central        
Susan Beal      eastern        
TJ Nichols      northeast      
Val Shultz      north          
The number of sales persons in the western region is 3.

注意:上述脚本跟书上的写法不尽相同,可能是因为awk版本的问题,书上的写法无法执行成功。

 

练习题

模板文件内容如下:

sh-4.2# cat lab5.data 
Mike Harrington:(510)548-1278:250:100:175
Christian Dobbins:(408)538-2358:155:90:201
Susan Dalsass:(206)654-6279:250:60:50
Archie McNichol:(206)548-1348:250:100:175
Jody Savage:(206)548-1278:15:188:150
Guy Quigley:(916)343-6410:250:100:175
Dan Savage:(406)298-7755:450:300:275
Nancy McNeil:(206)548-1278:250:80:75
John Goldenrod:(916)348-4278:250:100:175
Chet Main:(510)548-5258:50:95:135
Tom Savage:(408)926-3456:250:168:200
Elizabeth Stachelin:(916)440-1763:175:75:300

 

1、上面这个数据库的记录内容包括姓名、电话号码和近3个月的捐款数额。编写一个能产生如下输出的awk脚本。

sh-4.2# cat awk.sc
BEGIN{
    FS=":"
    OFS="\t"
    printf "\t\t***CAMPAIGN 1998 CONTRIBUTIONS***\n"
    printf "---------------------------------------------------------------------------------------\n"
    printf "NAME\t\t\t\tPHONE\t\tJan   | Feb   | Mar   | Total Donated\n"
    printf "---------------------------------------------------------------------------------------\n"
}
{
    printf "%-25s\t%13s\t%6.2f\t%6.2f\t%6.2f\t%6.2f\n",$1,$2,$3,$4,$5,$5
}

sh-4.2# awk -f awk.sc lab5.data 
        ***CAMPAIGN 1998 CONTRIBUTIONS***
---------------------------------------------------------------------------------------
NAME                PHONE       Jan   | Feb   | Mar   | Total Donated
---------------------------------------------------------------------------------------
Mike Harrington             (510)548-1278   250.00  100.00  175.00  175.00
Christian Dobbins           (408)538-2358   155.00   90.00  201.00  201.00
Susan Dalsass               (206)654-6279   250.00   60.00   50.00   50.00
Archie McNichol             (206)548-1348   250.00  100.00  175.00  175.00
Jody Savage                 (206)548-1278    15.00  188.00  150.00  150.00
Guy Quigley                 (916)343-6410   250.00  100.00  175.00  175.00
Dan Savage                  (406)298-7755   450.00  300.00  275.00  275.00
Nancy McNeil                (206)548-1278   250.00   80.00   75.00   75.00
John Goldenrod              (916)348-4278   250.00  100.00  175.00  175.00
Chet Main                   (510)548-5258    50.00   95.00  135.00  135.00
Tom Savage                  (408)926-3456   250.00  168.00  200.00  200.00
Elizabeth Stachelin         (916)440-1763   175.00   75.00  300.00  300.00

 

循环语句

循环的功能是:当测试表达式的条件为真时,重复执行表达式后面的语句。循环常常被用来对记录中的每个字段重复执行某种操作,或者在END块中用来循环处理某个数组中的所有元素。
awk有三种类型的循环:while循环、for循环和特殊for循环

 

while

  使用while循环的第一步是给一个变量初始值,然后在while表达式中进行测试该变量。如果求得表达式的值为真(非0),则进入循环体执行其中的语句。如果循环体内有多条语句,就必须用大括号把这些语句括起来。循环块结束之前,一定要更新用来控制循环表达式的变量,否则循环将无休止地进行下去。下面这个例子中,每处理一条新记录,循环控制变量就会被重置一次。do/while循环与while循环很相似,唯一的区别在于do/while要先执行循环体至少一次,然后才测试表达式。

 

示例
awk '{i = 1; while (i <= NF) {print NF,$i;i++}}' filename

说明
变量i被初始化为1;当i小于或者等于记录的字段数时,先执行print语句,然后将i加1。接下来又重新测试表达式,直至i大于NF的值。变量i要在awk开始处理下一条记录时被重置。

 

for

  for循环和while循环基本相同,只不过for循环的小括号中需要三个表达式,前两个分别是初始化表达式和测试表达式,第三个则用于更新测试表达式所用的变量。在awk的for循环中,小括号里的第一条语句只能初始化一个变量。

 

示例
awk '{for(i = 1;i <= NF;i++)print NF,$i}' filex

说明
变量i被初始化为1,然后测试它是否小于或者等于记录的字段数。若是,则print函数便打印出NF和$i的值,然后将i加1(for循环经常会在END操作中与数组一同使用,循环处理数组的所有元素)。

 

循环控制

循环控制语句分为breakcontinue

break:在某个特定条件为真时,使用break语句跳出循环。
continue:在特定条件为真时,让循环跳过continue后面的语句,将控制转回循环顶部,开始下一轮循环。

 

示例
1、{for (x = 3;x <= NF;x++)
if ( $x < 0 ){print "Bottomed out!",break}
}

2、{for (x = 3;x <= NF;x++)
if ( $x == 0 ){print "Get next item";continue}
}

说明
1、如果字段$x的值小于0,则break语句将控制跳转到循环体的右大括号后面的那条语句,即跳出循环。
2、如果字段$x的值等于0,则continue语句使控制转回循环顶部并开始执行,将从for循环的第三个表达式x++开始。

 

控制语句

next语句

  next语句从输入文件中取出下一行,然后awk脚本的顶部重新开始执行。

示例
(脚本)
{if ($1 ~ /Peter/){next}
  else {print}
}

说明:如果某一行的第一个字段包含Peter,awk就跳过该行,从输入行中读取下一行,然后从头开始执行脚本。

 

exit语句

  exit语句用于终止awk程序。它只能中断记录的处理,不能跳过END语句。如果exit语句的参数是一个0~255之间的数,这个值就会被打印在命令行上,以表明程序是否执行成功,并且指出失败的类型。

示例
(脚本)
{exit (1)}

#echo $status   (csh)
1

#echo $?    (sh/bash)
1

说明:退出状态为0表示成功,退出状态非0则表示失败。退出状态由程序员决定是否在程序中提供。在这个例子中,命令返回的退出状态值为1。

 

数组

  • 数组在awk中被称为关联数组,因为它的下标既可以是数字也可以是字符串。下标通常又被称作键(key),并且与对应的数组元素的值相关联。数组元素的键和值都存储在awk程序内部的一个表中,该表采用的是散列算法。正是由于使用了散列算法,所以数组元素不是顺序存储的,如果将数组的内容显示出来,元素的排列顺序也许跟想象中的不一样。
  • 数组和变量一样,数组也是被用到时才被创建,而且awk还能判定这个数组用于保存数字还是字符串。根据使用时的上下文环境,数组元素被初始化为数字0或者空字符串。数组的大小不需要声明。awk数组可用于从记录中收集信息,也可用于统计总数、计算词数、记录模式出现次数等应用。

 

关联数组的下标示例

sh-4.2# cat employees 
Tom Jones   4424    5/12/66 543354
Mary Adams  5346    11/4/63 28765
Sally Chang 1654    7/22/54 650000
Billy Black 1683    9/23/44 336500

1sh-4.2# awk '{name[x++]=$2}END{for(i=0;i<NR;i++){print i,name[i]}}' employees 
0 Jones
1 Adams
2 Chang
3 Black

2sh-4.2# awk '{id[NR]=$3}END{for(x=1;x<=NR;x++)print id[x]}' employees 
4424
5346
1654
1683

说明
1、数组name的下标是用户自定义的变量x。运算符++表明这是一个数值型的变量。awk将x初始化为0,并且每次使用x后将其加1。每条记录的第二字段都将赋值给数组name中的相应元素。END块使用for循环来处理数组,将从下标0开始,依次打印数组元素的值。下标只是一个键,所以不必从0开始。下标可以从任意值开始,数字或者字符串都可以。
2、awk变量NR保存当前记录的记录号。本例用NR作为下标,把每条记录的第三个字段赋值给数组中的相应元素。最后,for循环对数组进行循环处理,打印出保存在数组中的值。

 

特殊for循环

  当下标为字符串或者非连续的数字时,不能用for循环来遍历数组。这个时候就要用特殊for循环。特殊for循环把下标作为键来查找与之关联的值。格式如下:

{for (item in arrayname){
print arrayname[item]
}}

 

示例
sh-4.2# cat db2 
Tom Jones
Mary Adams
Sally Chang
Billy Black
Tom Savage
Tom Chung
Reggie Steel
Tommy Tucker

sh-4.2# awk '/^Tom/{name[NR]=$1}END{for(i=1;i<=NR;i++)print name[i]}' db
Tom



Tom
Tom

Tommy

说明
1、如果在输入行的行首匹配到正则表达式Tom,就为数组name赋值一个。NR值将作为name数组的索引。在每一行上匹配到Tom时,name数组就赋一个第一个字段的值。当达到END块时,name数组仅包含name[1]、name[5]、name[6]、name[8]这四个元素。因此,当使用for循环打印name数组的值时,索引2、34、7为空。
2、用特殊for循环遍历数组,只打印有相应下标的元素的值。打印结果的次序是随机的,因为关联数组是以散列方式存储的。

 

用字符串作为数组下标

数组下标可以是包含单个字符或字符串的变量组成,如果是字符串,则必须用双引号引起来。

 

示例
sh-4.2# cat datafile3 
tom
mary
sean
tom
mary
mary
bob
mary
alex

sh-4.2# cat awk.sc 
/tom/{count["tom"]++}
/mary/{count["mary"]++}
END{print "There are "count["tom"]" Toms in the file and " count["mary"]" Marys in the file."}

sh-4.2# awk -f awk.sc datafile3 
There are 2 Toms in the file and 4 Marys in the file.

说明
1、数组count包含两个元素:count["tom"]和count["mary"]。这两个数组元素的初始值都是0。每次匹配到tom时,数组元素count["tom"]的值都加1。
2、同样的过程被应用与count["mary"]。注意,每次只会算一次,即便tom或者mary在该行中出现多次。
3、END模式打印出每个数组元素的值。

 

用字段的值作为数组的下标

  任何表达式都可以用数组的下标。所以,也可以用字段作下标。如下范例中程序用计算所有名字在第二字段出现的次数,并引入了一种for循环的新模式。在之前介绍的例子中,END块中出现的for循环的工作过程如下:变量name被设为count数组的索引值,在每次for循环的迭代中,执行print操作,首先打印的是索引值,然后是保存在元素中的值(打印输出的次序是无法确定的)。

 

示例
sh-4.2# cat datafile4 
4234    Tom     43
4567    Arch        45
2008    Eliza       65
4571    Tom     22
3298    Eliza       21
4622    Tom     53
2345    Mary        24

sh-4.2# awk '{count[$2]++}END{for(name in count)print name,count[name]}' datafile4 
Arch 1
Tom 3
Eliza 2
Mary 1

说明
1、这条awk语句首先用记录的第二个字段作为数组count的下标。数组的下标随第二个字段的变化而变化,所以数组count的第一个下标是Tom,而count["Tom"]中保存的值是1。
2、然后,count["Arch"]、count["Eliza"]和count["Mary"]相继被设为1。当在第二个字段中再次出现Tom时,count["Tom"]的值被加1,于是它目前的值是2。Arch、Eliza和Mary再次出现时其过程类似。

 

示例
sh-4.2# cat datafile4 
4234    Tom     43
4567    Arch    45
2008    Eliza   65
4571    Tom     22
3298    Eliza   21
4622    Tom     53
2345    Mary    24

sh-4.2# awk '{dup[$2]++;if (dup[$2]>1){name[$2]++}}END{print "The duplicates were";for(i in name){print i,name[i]}}' datafile4
The duplicates were
Tom 2
Eliza 1

说明
数组dup的下标是第二个字段的值,即人名。dump数组中元素的值最初都是0,每处理一条记录,相应元素的值就加1。如果名字重复出现,则对应该下标的元素值就会变成2,并相应的逐渐增加。如果dup数组中某个元素的值大于1,就会创建一个名为name的新数组,也是第二个字段的值作为下标,用于记录出现次数大于1的人名。

 

数组与split函数

  awk的内置函数split能够将字符串拆分为词,然后保存在数组中。您可以指定字段分隔符,也就是FS的值。格式如下

split(字符串,数组,字段分隔符)
split(字符串,数组)

 

示例
sh-4.2# awk 'BEGIN{split("3/15/2004",date,"/");print "The month is "date[1]"and the year is "date[3]}' filename
The month is 3and the year is 2004

说明
将字符串3/15/2004保存到数组date中,用正斜杠作为字段分隔符。现在date[1]中是3,date[2]中15,而date[3]中则是2004。字段分隔符用3个参数指定,如未指定,就是以FS的值做字段分隔符。

 

delete函数

delete函数用于删除数组元素。

 

示例
awk '{line[x++]=$2END{for(x in line)delete(line[x])}}' filename

说明
赋给数组line的值是第二字段的值。所有记录都处理完后,特殊for循环将遍历数组的所有元素,并且由delete函数来删除它们。

 

多维数组

awk虽然没有宣称支持多维数组,却提供了定义多维数组的方法。awk定义多维数组的方法是把多个下标串成字符串,下标之间用内置变量SUBSEP的值分隔。变量SUBSEP的值是"\034",这是一个不可打印的字符,极少被使用,因此不太可能被用作下标中的字符。表达式matrix[2,8]其实就是数组matrix[2 SUBSEP 8],转换后所得的结果为matrix["2\0348"]。因此,下标成了关联数组中的唯一标识。

 

示例
(输入文件)
1 2 3 4 5
2 3 4 5 6
6 7 8 9 10

(脚本)
{nf=NF
for(x=1;x<NF;x++){
matrix[NR,x]=$x
}
}
END {for (x=1;x<=NR;x++){
for(y=1;y<=nf;y++)
printf "%d",matrix[x,y]
printf"\n"
}
}

(输出内容)
1 2 3 4 5
2 3 4 5 6
6 7 8 9 10

说明
1、将NF的值赋给变量nf
2、进入for循环,依次把输入行每个字段的字段号保存到变量x中。
3、matrix是一个二维数组。每个字段的值将赋给下标为NR和x的数组元素。
4、END块中的两个for循环被用来遍历matrix数组,并且打印数组中保存的值。这个例子只是用来说明如何使用多维数组。

 

处理命令行参数

ARGV:awk可以从内置数组ARGV中得到命令行参数,其中包括命令awk。但所有传递给awk的选项都不在其中。ARGV数组的下标从0开始。
ARGC:ARGC是一个包含命令行参数个数的内置变量。

 

示例
(脚本)
BEGIN{
for(i=0;i<ARGC;i++){
printf("argc[%d] is %s\n",i,ARGV[i])
}
printf("The number of  arguments,ARGC=%d\n",ARGC)
}
(输出)
awk -f argvs datafile
argv[0] is awk
argv[1] is datafile
The number of arguments,ARGC=2

说明
for循环先将i设为0,然后测试它是否小于命令行参数的个数(ARGC),再用printf函数依次显示出每个参数。所有参数处理完之后,最后那条printf语句用来输出参数的个数ARGC。这个例子说明awk并不把命令行选项视为参数。

 

示例
(命令行)
awk -f argvs datafile "Peter Pan" 12
argv[0] is awk
argv[1] is datafile
argv[2] is Peter Pan
argv[3] is 12
The number of arguments,ARGC=4

说明
和上个例子一样,打印出所有参数。awk命令被当成第一个参数,而-f选项和脚本文件名则被排除在外。

 

示例
(数据库)
cat datafile5
Tom Jones:123:03/14/56
Peter Pan:456:06/22/58
Joe Blow:145:12/12/78
Santa Ana:234:02/03/66
Ariel Jones:987:11/12/66

(脚本)
cat arging.sc
BEGIN{FS=":";name=ARGV[2]
print "ARGV[2] is "ARGV[2]
}
$1 ~ name{print $0}

(命令行)
awk -f arging.sc datafile5 "Peter Pan"
ARGV[2] is Peter Pan
Peter Pan:456:06/22/58
awk:can't open Peter Pan
input record number 5,file Peter Pan
source line number 2

说明
1、在BEGIN块中,ARGV[2]的值,即Peter Pan,被赋给变量name。
2、Peter Pan被打印出来了,但是,处理完datafile并将其关闭后,awk试图把Peter Pan作为输入文件打开。awk把参数都作为输入文件。

 

示例
(脚本)
cat arging2.sc
BEGIN{FS=":";name=ARGV[2]
print "ARGV[2] is "ARGV[2]
delete ARGV[2]
}
$1 ~ name{print $0}

(命令行)
awk -f arging2.sc datafile "Peter Pan"
ARGV[2] is Peter Pan
Peter Pan:456:06/22/58÷

说明
awk把ARGV数组的元素作为输入文件。且awk用完一个参数就将它左移,接着处理下一个,知道ARGV数组边控。如果某个参数使用后立刻被删除,那么这个参数就不会被当做下一个输入文件来处理。

 

内置函数

字符串函数(sub和gsub函数)

  sub函数用于在记录中查找能够匹配正则表达式的最长且最开做的子串,然后用替换串取代找到的子串。如果指定了目标串,就在目标船中查找能够匹配正则表达式的最长且最靠左的字串,并将找到的字串替换为替换穿,若未指定目标串,则在整个记录中查找。格式如下:

sub (正则表达式,替换串);
sub (正则表达式,替换串,目标串);
gsub (正则表达式,替换串);
gsub (正则表达式,替换串,目标串);

 

示例
1awk '{sub(/Mac/,"MacIntosh");print}' filename
2awk '{sub(/Mac/,"MacIntosh",$1);print}' filename

说明
1、在记录$0中第一次匹配到正则表达式Mac时,Mac被替换成字符串MacIntosh。sub函数只对每行中出现的第一个匹配字符串进行替换。
2、在记录的第一个字段中第一次匹配到正则表达式Mac时,Mac被替换成字符串MacIntosh。sub函数值对目标串中出现的第一个匹配字符串进行替换。gsub函数则对字符串中的正则表达式进行全局替换,即替换所在记录$0中出现的正则表达式。

 

示例
1awk '{gsub(/CA/,"California");print}' datafile
2awk '{gsub(/[Tt]om/,"Thomas",$1);print}' filename

说明
1、记录$0中找到的每一个正则表达式CA都被替换为California。
2、在第一个字段中找到的每个正则表达式Tom或者tom都被替换为Thomas。

 

index函数

index函数返回子串在字符串中第一次出现的位置。偏移量从位置1开始计算。格式如下:

index(字符串,子串)

 

示例
awk '{print index("hollow","low")}' filename
4

说明
返回的数字是子串low在字符串hollow中第一次出现的位置,偏移量从1开始计算。

 

length函数

length函数返回字符串中字符的个数。如果未指定参数,则length函数返回记录中的字符个数。格式如下:

length(字符串)
length

 

示例
awk '{print length("hello")}' filename
5

说明
length函数返回字符串hello的字符个数。

 

substr函数

substr函数返回从字符串指定位置开始的一个子串。如果指定了子串的长度,则返回字符串的相应部分。如果指定的长度超出了字符串的实际范围,则返回其实际内容。格式如下:

substr (字符串,起始位置)
substr (字符串,起始位置.子串长度)

 

示例
awk '{print substr("Santa Claus",7,6)}' filename
Claus

说明
在字符串"Santa Claus"中,打印从位置7开始、长度为6个字符的字串。

 

match函数

match函数返回正则表达式在字符串中出现的位置,如果未出现,则返回0。match函数把内置变量RSTART设为子串在字符串中的起始位置,RLENGTH则设为子串的长度。这些变量可以被substr函数用来提取相应模式的字串(只可用于nawk)。格式如下:

match (字符串,正则表达式)

 

示例
awk 'END{start=match("Good ole USA",/A-Z+$/);print start}' filename
10

说明
正则表达式/A-Z+$/的意思是查找在字符串尾部连续出现的大写字母。找到的子串USA是从字符串"Good ole USA"的第十个字符开始的。如果字符串未能匹配到正则表达式,则返回0。

 

示例
1awk 'END{start=match("Good ole USA",/[A-Z]+$/);print RSTART,RLENGTH}' filename
10 3

2awk 'BEGIN{line="Good ole USA"};END{match(line,/A-Z+$/);print substr(line,RSTART,RLENGTH)}' filename
USA

说明
1、变量RSTART被match函数设置为匹配到的正则表达式在字符串中的起始位置。变量RLENGTH则被设为字串的长度。
2、substr函数在变量line中查找子串,把RSTART和RLENGTH的值作为子串的起始位置和长度。

 

split函数

split函数使用由第三个参数指定的字段分隔符,把字符串拆分成一个数组。如果没有提供第三个参数,awk将把FS的当前值作为字段分隔符。格式如下:

split (字符串,数组,字段分隔符)
split (字符串,数组)

 

示例
awk 'BEGIN{split("12/25/2001",date,"/");print date[2]}' filename
25

说明
split函数把字符串12/25/2001拆分为数组date,以正斜杠作为字段分隔符。数组date的下标从1开始。awk将打印数组date的第二个元素。

 

sprintf函数

sprintf函数返回一个指定格式的表达式。可以在sprintf函数中使用printf的格式规范。格式如下

variable = sprintf("含有格式说明的字符串",表达式,表达式2.....)

 

示例
awk '{line = sprintf ("%-15s %6.2f",$1,$3);print line}' filename

说明
按照printf的规范设置第一个和第三个的字段。结果被赋给用户自定义的变量line。

 

算术函数

整数函数(int函数)

int函数将社区小数点后面的所有数字,生成一个整数。int函数不执行四舍五入操作。

 

示例
1awk 'END{print 31/3}' filename
10.3333

2awk 'END{print int(31/3)}' filename
10

说明
1、END块将除法运算的结果打印成浮点数形式。
2、END块中的int函数把除法运算的结果从小数点开始社区,显示的结果是一个整数。

 

随机数生成器(rand函数)

rand函数生成一个大于或者等于0、小于1的伪随机浮点数。

 

示例
awk '{print rand()}' filename
0.513871
0.175726
0.308634

awk '{print rand()}' filename
0.513871
0.175726
0.308634

说明
每次运行程序都打印出同一组数字。可以用srand函数可以为rand函数的种子设一个新值,否则,如上例所示,每次调用rand都只会重复出现同意数列。

 

srand函数

srand函数如果未指定参数,srand函数会根据当前时刻为rand函数生成一个种子。srand(x)则把x设置成种子。通常,程序应该在运行过程中不断的改变x的值。

 

示例
awk 'BEGIN{srand();{print rand()}}' filename
0.508744
0.639485
0.657277

awk 'BEGIN{srand();{print rand()}}' filename
0.133518
0.324747
0.691794

说明
srand函数为rand设置了一个新种子,起点是当前时刻。因此,每次调用rand都打印出一组新的数列。

 

示例
awk 'BEGIN{srand()};{print 1 + int(rand() * 25)}' filename
6
24
14

说明
strand 函数为rand设置了一个新种子,起点是当前时刻。rand函数在0~25之间选出一个随机数,然后将其化为整数。

 

自定义函数(nawk)

脚本中凡是可以出现模式操作规则的位置都可以放置用户自定义的函数。格式如下:

函数名(参数,参数){
语句
return 表达式
(注意:return语句和表达式都是可选项)
}

 

  变量以参数值的方式传递,且仅在使用它的函数中局部有效。函数使用的只是变量的副本。数组则通过地址或者引用被传递,因此,可以在函数中直接修改数组的元素。函数中的任何变量,只要不是从参数列表中传来,就都会被视为全局变量,也就是说,该变量对整个awk程序都是课件的,而且,如果它在>函数中发生了改变,即在整个程序中发生了改变,在函数中提供局部变量的唯一途径就是将它们加入参数列表中。这类参数通常放在参数列表的末端。当条用函数时,如果没有指定某个形参的值,该参数就会被初始化为空。return语句会把控制权交还给调用者,可能还会返还一个值。

 

示例
(示例文件)
cat grades
44 55 66 22 77 99
100 22 77 99 33 66
55 66 100 99 88 45

(脚本文件)
cat sorter.sc
function sort (scores,num_elements,temp,i,j){
for (i=2;i<=num_elements;++i){
        for (j=i;scores [j-1] > sores[j]){
        temp = scores[j]
        scores[j]=scores[j-1]
        scores[j-1]=temp

}
}
}
{for (i=1;i<=NF;i++)
        grades[i=$i]
        sort(grades,NF)
        for (j=1;j<=NF;++j)
        printf ("%d",grades[j])
        printf ("\n")
}

说明
1、定义名为soft的函数。函数定义可以出现在脚本的任意位置。除了那些作为参数传递的变量外,所有其他变量的域都是全局的。即如果在函数中发生了变化,也就是在整个awk脚本中发生了变化。数组是通过引用进行传递的。小括号中共有5个形参,其中:数组scores将通过引用被传递,所以,如果在函>数中修改了这个数组中任何一个元素,原来的数组也会被修改。变量num_elements是一个局部变量,是原变量的一个副本。变量temp,j和i则是函数的局部变量。
2、外层的for循环将遍历一个证书数组,前提是该数组中至少有两个整数可用于比较。
3、内层的for循环用当前这个整数与数组中前一个整数(scores[j-1])进行比较。如果前一个整数大于当前这个整数,就把当前这个数组元素的值赋给变量temp,然后把前一个元素的值赋给当前元素。
4、外层循环至此结束。
5、函数定义的末尾。
6、脚本的第一个操作块由此开始。for循环遍历当前记录的所有字段,生成一个整数数组。
7、调用sort函数,把由当前记录生成的整数数组和当前记录的字段数作为参数传给它。
8、sort函数结束后,程序控制由此开始。这个for循环用于打印完成排序的数组中的元素。

 

复习

如果没有特别说明,本节范例都将使用下面的datafile数据库。

cat datafile
northwest   NW  Joel Craig      3.0 .98 3   3
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

 

范例1:if语句属于操作语句。如果表达式后面的语句不止一条,就必须用大括号将它们括起来。这个表达式的含义是:如果第八个字段大于15,则打印第三个字段和字符串"has a high rating",否则就打印第三个字段和字符串"---NOT A COMPETITOR---"。

sh-4.2#  awk '{if($8>15){printf $3" has a high rating\n"}else{print $3"---NOT A COMPETITOR---\n"}}' datafile
Joel---NOT A COMPETITOR---

Sharon has a high rating
Chris has a high rating
May---NOT A COMPETITOR---

Derek has a high rating
Susan has a high rating
TJ---NOT A COMPETITOR---

Val---NOT A COMPETITOR---

Sheri---NOT A COMPETITOR---

 

范例2:用户自定义变量i被赋值为1。awk进入while循环,开始测试表达式。如果表达式值为真,就执行print语句,打印第i个字段的值。然后将变量i的值加1,再次进入循环。当i的值大于NF且NR为2或者更大的值时,循环表达式的值就会变为假。变量i的值会在开始处理下一条记录时被重新初始化。

sh-4.2# awk '{i=1;while(i<=NF && NR < 2){print $i;i++}}' datafile
northwest
NW
Joel
Craig
3.0
.98
3
4

 

范例3:for循环在功能上类似于while循环。它把初始化、测试和循环控制语句都放在一个表达式里面。for循环先为当前记录初始化一次i的值(i=3),然后测试表达式。如果i小于或者等于NF,且NR等于3,则执行print块。打印第i个字段的值之后,控制就转回循环表达式,将i的值加1,然后再次执行测试。

sh-4.2# awk '{for(i=3;i<=NF && NR == 3;i++){print $i}}' datafile
Chris
Foster
2.7
.8
2
18

 

范例4:数组list以NR作为下标的值。每处理一行输入,都将其第一个字段赋值给数组list。END块中的for循环对每个数组元素执行遍历和打印操作。

sh-4.2# awk 'BEGIN{OFS="\t"}{ list[NR] = $1 }END{ for(n = 1;n<=NR;n++){print list[n]}}' datafile
northwest
western
southwest
southern
southeast
eastern
northeast
north
central

 

范例5:每次在输出行中发现正则表达式north时,都把该行的第三个字段赋给数组name。每处理一条新纪录,数组的下标count都被加1,于是生成一个新的数组元素。然后,END块采用一个特殊的for循环来遍历该数组。

 

范例6:数组region以第一个字段作为下标,它保存的值是每个区的出现次数。然后,END块用一个特殊的for循环来遍历并且打印region数组。

sh-4.2# awk '/north/{name[count++]=$3;}END{for(i in name){print name[i]} }' datafile
Joel
TJ
Val

 

练习题

(数据库文件lab5.data)

Mike Harrington:(510)548-1278:250:100:175
Christian Dobbins:(408)538-2358:155:90:201
Susan Dalsass:(206)654-6279:250:60:50
Archie McNichol:(206)548-1348:250:100:175
Jody Savage:(206)548-1278:15:188:150
Guy Quigley:(916)343-6410:250:100:175
Dan Savage:(406)298-7755:450:300:275
Nancy McNeil:(206)548-1278:250:80:75
John Goldenrod:(916)348-4278:250:100:175
Chet Main:(510)548-5258:50:95:135
Tom Savage:(408)926-3456:250:168:200
Elizabeth Stachelin:(916)440-1763:175:75:300

上面这个数据库的记录包括姓名、电话号码和最近三个月的竞选捐款数额。请编写一个能产生如下输出的awk脚本。

 

杂项

有些数据(比如从磁带或者电子表格)可能没有明显的字段分隔符,却有固定宽度的列。预处理这类数据时,substr函数很管用。

 

固定字段

下面这个例子中,字段都是固定宽度的,但没有使用字段分隔符。substr函数可以用来创建字段。

cat fixed
031291ax5633(408)987-0124
021589bg2435(415)866-1345
122490de1237(916)933-1234
010187ax3458(408)264-2546
092491bd9923(415)134-8900
112990bg4567(803)234-1456
070489qr3455(415)899-1426

awk '{printf substr($0,1,6)}" ";printf substr($0,7,6)" ";print substr($0,13,length)}' fixed

说明
第一个字段通过从整个记录中提取子串得到,子串从记录第一个字段开始、长度为6个字符。接下来,打印一个空格。第二个字段是通过在记录中提取从位置7开始、长度为6个字符的子串得到,后跟一个空格。最后一个字段则是通过在整个记录中提取从位置13开始、到由行的长度所确定的位置之间的子串获得。



空字段

如果用固定长度的字段来存储数据,就可能出现一些空字段。下面这个例子中,substr函数被用来保存字段,而不考虑它们是否包含数据。

cat db
xxx xxx 
xxx abc xxx
xxx a   bbb
xxx     xx

cat awkfix
{
f[1]=substr($0,1,3)
f[2]=substr($0,5,3)
f[3]=substr($0.9.3)
line=sprintf("%-4s%-4s%-4s\n",f[1],f[2],f[3])
print line
}

awk -f awkfix db

说明
1、打印文件db的内容。这个文件中有一些空字段。
2、数组f的第一个元素被赋值为由位置1开始、长度为3的记录的子串。
3、数组f的第二个元素被赋值为由位置5开始、长度为3的记录的子串。
4、数组f的第三个元素被赋值为由位置9开始、长度为3的记录的子串。
5、用sprintf函数设置好数组元素的格式,然后将它们赋值给用户自定义的变量line。
6、打印line的值,可以看到结果中空字段依然被保留。

 

带$、逗号或者其他字符的数字

下面这个例子中,价格字段中包含一个美元符号和逗号。脚本必须删除掉这些字符,才能把价格加起来得出总的开销。可以通过gsub函数来完成这一任务。

cat
access tech:gp237221:220:vax789:20/20:11/01/90:$1,043.00
alias systems:bp262292:280:macintosh:now updates:06/30/91:$456.00
alias systems:gp262345:260:vax8700:alias talk:02/03/91:$1,598.50
apple computer:zx342567:240:macs:e-mail:06/25/90:$575.75
caci:gp262313:280:sparc station:network11.5:05/12/91:$1,250.75
datalogics:bp132455:260:microvax2:pagestation maint:07/01/90:$1,200.00
dec:zx354612:220:microvax2:vms sms:07/20/90:$1,350.00

awk -F: '{gsub(/\$/,""):gsub(/,/,"");cost +=$7};END{print "The total is $" cost}' vendor
$7474

说明
第一个gsub函数用空字符串对美元符号进行全局替换;第二个gsub函数则用空串替换全部逗号。然后,将用户自定义变量cost与每行的第七个字段相加,再把每次的结果赋回给cost,由此统计出总数。END块打印出字符串"The total is $",后面跟着cost的值。

 

多行记录

到目前为止,本书用作例子的所有数据文件中,每条记录都自成一行。而在下面这个名为checkbook的示例数据文件中,记录之间用空行分隔,统一记录的字段之间则用换行符号分隔。要处理这个文件,就必须将记录分隔符设为空值,而把字段分隔符设为换行符。

(输入文件)
cat checkboo
1/1/04
#125
-695.00
Mortgage

1/1/04
#126
-56.89
PG&E
1/2/04
#127
-89.99
Safeway

1/3/04
+750.00
Paycheck

1/4/04
#128
-60.00
Visa

(脚本)
cat awkchecker
BEGIN{RS="";FS="\n";ORS="\n\n"}
{print NR,$1,$2,$3,$4}

(输出)
awk -f awkchecker checkbook


说明
1、在BEGIN块中,记录分隔符RS被赋值为空,字段分隔符被设为换行符,输出记录分隔符ORS则被设置为两个换行符。语句,每一行都是一个字段,且输出记录之间有两个换行符将其分隔。
2、打印记录号,后跟记录的每个字段。

 

生成格式信函

  下面这个例子改变自The awk programming Language中的一个程序。其复杂之处在于对正贼处理的数据进行记录。输出文件的名称是data.form,他只是包含数据,文件中的字段由冒号分隔。另一个文件叫做form.letter,其内容是用于生产信函的实际格式。这个文件由getline函数加载进入awk的内存空间。格式信函的每一行都被保存在一个数组中。这个程序从data.form中读取数据,用实际数据替换form.letter中以#和@开头的特殊字符串,从而生成信函。临时变量temp保存了数据替换完成后,要显示的实际的行内容。这个程序可以用来为data.form中列出的每个生成一封个人信函。

(awk脚本)
BEGIN{FS=":";n=1
    while (getline < "from.letter" > 0)
    from[n++] = $0
"date"|getline d; split(d,today," ")

thisday=today[2]". "today[3]","today[6]
}
{for (i=1;i<n;i++){
    temp=form[i]
    for (j=1;j<=NF;j++){
    gsub("@date",thisday,temp)
    gsub("#"j,$j,temp)
}
print temp
}
}

cat form.letter
    The form letter,form.letter,looks like this:
************************************************************
    Subject:Status Report for Project "#1"
    To:#2
    From:#3
    Date:@date
    This letter is to tell you,#2,that project "#1" is up to date.
    We expect that everything will be completed and ready for shipment as scheduled on #4.

    Sincerely,
    #3
***********************************************************
  The file,data.form,is awk's inputfile containing the data that will replace the #1-4
    and the @date in form.letter.

cat data.form
    Dynamo:John Stevens:Dana Smith,Mgr:4/12/2004
    Gallactius:Guy Sterling:Dana Smith,Mgr:5/15/2004

(命令行)

awk -f form.awk data.form

说明
1、在BEGIN块中,字段分隔符被设置为冒号。用户自定义变量n被设置为1.
2、在while循环中,getline函数逐行读入文件form.letter。如果没有找到指定文件,getline就会返回-1.读到文件末尾时,getline返回。因此只要测试getline的返回值是大于0,就能知道该函数是否从输入文件中读出一行数据。
3、从form.letter中读到每一行都被赋值给数组form。
4、unix命令date的输出被管道发给getline函数,并赋值给用户自定义变量d。然后,split函数用空格拆分变量d,生成一个名为today的数组。
5、把数组today中月、日、年的值赋值给用户自定义变量thisday。
6、BEGIN块结束。
7、for循环将要循环n次。
8、从数组form中读取一行并赋值给用户自定义变量temp。
9、这个嵌套的for循环将对来自输入文件data.form的记录执行NF次循环。在变量temp保存的每一行中查找字符串"@date",如果匹配,就用gsub函数把它替换成当天的日期。
10、如果在temp保存的行中发现了字符#和一个数字相连,gsub函数就将#和这个数字替换为输入文件data.form中相应字段的值。例如,如果测试的对象是第一行,则#1将替换为Dynamo,#2将替换为John Stevens,#3替换为Dana Smith,#4替换为4/12/2001,以此类推。
11、替换后,对temp中国存储的行进行打印。

 

与shell交互

我们已经知道了awk是如何工作的,同时也了解到编写shell脚本时,awk将是一个非常强大的工具。你可以在shell脚本中切入单行的awk命令或者awk脚本。下面就是一个切入了awk命令的Korn shell程序。

#!/bin/ksh
print "Hello $LOGNAME"
print "This report is for the month and year:"
cal | awl 'NR==1{print $0}'
if [[ -f data.form || -f formletter? ]]
  then
    rm data.form formletter? 2> /dev/null
fi

integer num=1
while true
do
    print "Form letter #$num:"
    read project?"What is the name of the project? "
    read sender?"Who is the status report from"
    read recipient?"Who is the status report to?"
    read due_date?"Whatt is the completion date scheduled?"
    echo $project:$recipient:$sender:$due_date > data.form
    print -n "Do you whish to generate another form letter?"
    read answer
if [[ "$answer" != [[Yy]* ]]
  then
    break
else
    awk -f form.awk data.form > formletter$num
fi
(( num+=1 ))
done

awk -f form.awk data.form > formletter$num

说明
1、把unix命令cal的输出通过管道发送给awk。打印输出的第一行,其中包邮当前的月份和年份。
2、awk脚本form.awk生成的格式信函被重定向到一个unix文件。

 

练习题

(参见从本书合作站点下载的文件名中名为lab7.data的数据库文件)

Mike Harrington:(510)548-1278:250:100:175
Christian Dobbins:(408)538-2358:155:90:201
Susan Dalsass:(206)654-6279:250:60:50
Archie McNichol:(206)548-1348:250:100:175
Jody Savage:(206)548-1278:15:188:150
Guy Quigley:(916)343-6410:250:100:175
Dan Savage:(406)298-7755:450:300:275
Nancy McNeil:(206)548-1278:250:80:75
John Goldenrod:(916)348-4278:250:100:175
Chet Main:(510)548-5258:50:95:135
Tom Savage:(408)926-3456:250:168:200
Elizabeth Stachelin:(916)440-1763:175:75:300

上面这个数据库所记录的内容包括姓名、电话号码和最近3个月的竞选捐款数额。请编写一个用户自定义函数,要求该函数能返回指定月份的人均捐款额。月份由用户在命令行输入。

 

范例

本节示例,除特别声明外,都是用了下面这个重复出现过多次的datafile文件。

northwest   NW  Joel Craig      3.0 .98 3   4
western     WE  Sharon Kelly    5.3 .97 5   23
southwest   SW  Chris Foster    2.7 .8  2   18
southern    SO  May Chin        5.1 .95 4   15
southeast   SE  Derek Johnson   4.0 .7  4   17
eastern     EA  Susan Beal      4.4 .84 5   20
northeast   NE  TJ Nichols      5.1 .94 3   13
north       NO  Val Shultz      4.5 .89 5   9
central     CT  Sheri Watson    5.7 .94 5   13

 

 

字符串函数
范例1
awk 'NR==1{gsub(/northwest/,"southeast",$1);print}' datafile

说明
如果这是第一条记录,就将其第一个字段中的正则表达式notrhwest全部替换为southwest。当然,前提是第一个字段中有northwest。

范例2
awk 'NR==1{print substr($3,1,3)}' datafile

说明
如果这是第一条记录,就显示其第三个字段中从第一个字段开始、长度为3个字符的子串。显示结果是子串"Joe"。

范例3
awk 'NR==1{print length($1)}' datafile

说明
如果这是第一条记录,就打印其第一个字段的长度。

范例4
awk 'NR==1{print index($1,"west")}' datafile

说明
如果这是第一个记录,就打印其第一个字段中首次出现子串west的位置。字符串west是从字符串notrhwest的第六个位置开始。

范例5
awk 'if(match($1,/^no/)){print substr($1,RSTART,RLENGTH)}' datafile

说明
如果match函数在第一个字段中找到了正则表达式/^no/,就返回最左端那个字符的索引位置。match函数还将内置变量RSTART设置为索引位置,将变量RLENGTH设为匹配到的子串的长度。substr函数返回第一个字段中从位置RSTART开始、长度为RLENGTH个字符的子串。

范例6
awk 'BEGIN{split("10/14/04",now,"/");print now[1],now[2],now[3]}'

说明
字符串10/14/01被炒粉成为数组now。分隔符是正斜杠。从第一个元素开始打印数组的所有元素。

范例7
cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

awk -F: '/north/{split($1,name," ");print "First name: "name[1];print "Last name: "name[2];print "\n-------------------------"}' datafile2

说明
输入字段分隔符被设为冒号。如果记录中包含正则表达式north,就用空格作为分隔符,将该记录的第一字段拆分为数组name。然后打印数组name的元素。

范例8
说明
sprintf函数以printf函数的格式规范来设置第七和第二个字段的格式。接着,返回经过格式化的字符串,并将其赋值给用户自定义的变量line。然后,变量line被打印出来。
awk '{line=sprintf("%10.2f%5s\n",$7,$2);print line}' datafile

toupper和tolower函数(仅适用于gawk)
toupper函数返回的是一个字符串。其中,所有小写字母均已转换成大写字母,非字母字符不发生变化。类似的,tolower也返回字符串,但作用于将所有大写字母转换成小写字母。注意,字符串必须加双引号。

格式如下:
toupper (字符串)
tolower (字符串)

范例8
awk 'BEGIN{print toupper("Linux"),tolower("BASE 2.0")}'

gawk的时间函数
gawk提供了两个函数来获取时间和格式化时间戳:systime和strftime。
systime函数:将返回来自1970年1月1日以来经过的时间(按照秒计算)。

格式如下
systime()

范例9
awk 'BEGIN{noe=systime();print now}'

说明
systime函数的返回值被赋给一个用户自定义的变量:now。这个值等于从1970年1月1日以来所累计的总时间。

strftime函数
strftime函数使用C库中的strftime函数对时间进行格式化。格式形式可以为%T%D等。时间戳的格式和systime函数返回值所采用的格式一样,如果不适用时间戳,则以当前的时间为默认时间。

格式如下:
systime([format specification][timestamp])

范例10
awk 'BEGIN{now=strftime("%D",systime());print now}'

awk 'BEGIN{now=strftime("%T");print now}'

awk 'BEGIN{now=strftime("%m/%d/%y");print now}'

说明
strftime函数通过一个参数所给出的格式来设置时间和日期的形式。如果以systime作为第二个参数,或者不带第二个参数,使用本地的当前时间。如果带了第二个参数,则它必须与systime函数的返回值格式一致。

命令行参数
范例11
cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

cat argvs.sc
BEGIN{

    for=(i=0;i<ARGC;i++)
    printf("argv[%d] is %s\n",i,ARGV[i])
    printf("The number of arguments,ARGC=%d\n",ARGC)
}

awk -f argvs.sc datafile


说明
BEGIN块中包含一个for循环,用于处理命令行参数。ARGC是参数的个数,ARGV则是包含实际参数的数组。awk不把选项当成参数。这个例子中的有效参数只有awk命令和输入文件datafile。

范例12
cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

awk 'BEGIN{name=ARGV[1]};$0 ~ name {print $3,$4}' "Derek" datafile

awk 'BEGIN{name=ARGV[1];delete ARGV[1]};$0 ~ name{print $3,$4}' "Derek" datafile

说明
1、在BEGIN块中,名字"Derek"被赋给变量name。接下来的模式操作块中,name试着将"Derek"作为输入文件打开,结果失败了。
2、把"Derek"赋给变量name后,awk就把ARGV[1]删除了。进入模式操作块时,awk没有尝试将"Derek"作为输入文件打开,而是打开了文件datafile。

读输入(getline)
范例13
awk 'BEGIN{"date"|getline d;print d}' datafile

说明
将linux的date命令通过管道传给getline函数,结果保存在变量d中并打印出来。

范例14
awk 'BEGIN{"date"|getline d;split(d,mon);prnt mon[2]}' datafile

说明
将linux的date命令通过管道传给getline函数,结果保存在变量d中,split函数将字符串d拆分为数组mon。然后,awk打印数组mon的第二个元素。

范例15
awk 'BEGIN{printf "Who are you looking for?";getline name < "/dev/tty"}'

说明
从终端/dev/tty读取输入,保存到数组name中。

范例16
awk 'BEGIN{while(getline<"/etc/passwd">0){lc++};print lc}' datafile

说明
用while循环逐行遍历/etc/passwd文件,每进入一次循环,都用getline函数读入一行,同时将变量lc加1.退出循环后,打印lc的值,即打印出文件/etc/passwd的行数。只要getline的返回值大于0,即读入一行,循环就会继续。

控制函数
范例17
awk '{if($5 >= 4.5)next;print $1}' datafile

说明
如果第五个字段大于4.5,就读入输入文件的下一行,并从awk脚本的起点开始处理。否则,打印第一个字段。

范例18
cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

awk '{if ($2 ~ /s/){print ; exit 0 }}' datafile

echo $status (CSH)
echo $? (sh or ksh)

说明
如果记录的第二个字段包含字母S,就打印该记录,并且从awk程序退出。C shell的变量status中保存了退出状态。如果是Bourne shell 或者Korn shell,退出状态则保存在变量$?中。

用户自定义函数
范例19
cat awk.sc7
BEGIN{largest=0}
{maximum=max($5)}

function max (num) {
    if (num > largest){largest=num}
    return largest
}
END{ print "The maximum is "maximum"."}

awk -f awk.sc7 datafile

说明
1、用户自定义变量在BEGIN块中被初始化为0.
2、处理文件中的每一行时,都已$5为参数调用函数max,并将其返回值赋给变量maximum。
3、定义用户自定义函数max,函数的语句必须括在大括号中。每次从输入文件datafile中读取新的记录后,脚本都会调用max函数。
4、比较num和largest的值,返回其中较大的值。
5、函数定义块结尾。
6、END块打印maximum最终的值。

 

转载于:https://www.cnblogs.com/guge-94/p/11010021.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值