AWK模式处理语言

时间:2021-08-23 12:47:29

AWK模式处理语言

简介

AWK——一个强大的文本分析工具,是一种模式扫描和处理语言,它搜索一个或者多个文件,以查看这些文件中是否存在匹配指定模式的记录(通常是文本行)。每次发现匹配记录时,它通过执行动作的方式(比如将该记录写到标准输出或者将某个计数器递增)来处理文本行。与过程语言相反,AWK属于数据驱动语言:用户描述想要处理的数据并告诉AWK当它发现这些数据时如何处理他们。

使用AWK可以生成报告或者过滤文本。它在处理时不区分数字和文本,如果将两者混在一起,AWK通常可以得出正确的答案。

awk有3个不同版本: awk、nawk和gawk,一般指gawk,gawk 是 AWK 的 GNU 版本。

AWK的作者:Alfred V. Aho、Peter J. Weinberger、Brian W.Kernighan

语法

gawk [options] [program] [file-list]

gawk [options] -f program-file [file-list]

参数

program是用户在命令行中包含的gawk程序。

program-file是存放gawk程序的文件的名称。在命令行上使用gawk,就可以编写出简短的gawk程序,而不用创建单独的program-file文件。为了防止shell将gawk命名解释成shell命令,要将program用单引号引起来,将较长或者较复杂的程序放在文件中可以减少错误和重复输入。

file-list包含gawk要处理的普通文件的路径名。这些文件就是输入文件。如果用户没有指定file-list,gawk就从标准输入或者由getline或协进程指定输入。

选项

选项 选项 含义
–field-seperator fs -F fs 将fs作为输入字段分隔符(FS变量)的值。
–file program-file -f program-file 从program-file文件中而不是命令行中读取gawk程序。用户可以在命令行上多次指定这个选项。
–help -W help 总结如何使用gawk(仅用于gawk)。
–lint -W lint 对不正确或者能移植的结构发出警告(仅用于gawk)。
–posix -W posix 运行POSIX兼容版gawk。这个选项引入了一些限制。
–traditional -W traditional 忽略gawk程序中较新的GNU特性,使得程序与UNIX awk兼容(仅用于gawk)。
–assign var=value -v var=value 把value赋予变量var。在gawk程序执行之前进行赋值,它可以用于BEGIN模式中。可以在命令行上多次指定这个选项。

语言基础

gawk程序由一行或者多行文本构成,其中包含一个模式和/或者动作,格式如下:

pattern { action }

模式(pattern)用来从输入中选取文本行。对于由模式选中的每行文本,gawk实用程序都执行动作(action)。动作两边的花括号使gawk将动作与模式区分开来。如果程序行没有包含模式,gawk就选择输入中的所有行。如果程序行没有包含动作,gawk就把选中的行复制到标准输出中。

模式

!~ 用斜杠把正则表达式括起来,就可以将其看做模式。~运算符用于测试某个字段或者变量是否匹配正则表达式。!~运算符用于测试不匹配。可以使用关系运算符进行数值比较和字符串比较。可以使用布尔运算符||(OR)或者&&(AND)来组合任何模式。

BEGINEND BEGIN和END是两种独特的模式,分别执行在gawk开始处理输入信息之前和处理完毕输入信息之后的命令。在处理所有输入信息之前,gawk实用程序执行BEGIN模式关联的动作,在处理完之后执行END模式关联的动作。

,(逗号) 逗号是范围运算符。如果在一个gawk程序行上用逗号将两种模式隔开,gawk就选取从匹配第1种模式的第1行开始的一系列文本行。gawk选取的最后一行是随后匹配第2种模式的下一行文本。如果没有匹配第2种模式的文本行,gawk就选取直到输入末尾的所有文本行。在gawk找到第2中模式之后,它将再次查找第1中模式以再次开始这个过程。

动作

如果gawk匹配某种模式,它就执行gawk命令的动作部分所指定的动作。如果没有指定动作,gawk就执行默认动作,即print命令(可用{print}显式表示)。这个动作将记录从输入复制到标准输出。

注释

使用#开头可以不处理程序行后面的内容。

变量

尽管不需要在使用gawk变量之前声明它们,但用户可以选择把初始值赋予这些变量。
没有赋值的数值变量被初始化为0,而字符串变量则被初始化为空字符串。
除了支持用户变量(user varialbe)之外,gawk维护程序变量(program variable)。在gawk程序的模式部分和动作部分中均可以使用用户变量和程序变量。

如下是一些程序变量

变量 含义
$0 当前记录(作为单个变量)
1  n 当前记录中的字段
FILENAME 当前输入文件的名称(null表示标准输入)
FS 输入字段分隔符(默认为空格或制表符)
NF 当前记录的字段数目
NR 当前记录的记录编号
OFS 输出字段分隔符(默认为空格)
ORS 输出记录分隔符(默认为换行)
RS 输入记录分隔符(默认为换行)



除了在程序中初始化变量之外,还可以在命令行上使用–assign(-v)选项初始化变量。如果某个变量的值在gawk的两次运行之间发生改变,这个功能非常有用。

记录分隔符:默认情况下,输入记录和输出记录的分隔符均为换行符。因此gawk将每行输入作为单独的一个记录,并在每条输出记录后面追加一个换行符。默认情况下,输入字段分隔符为空和制表符。默认的输出字段分隔符是空格。在任意时刻都可以更改分隔符的值,方法是在程序中或者命令行中使用–assign(-v)选项,将一个新的值赋予与这些分隔符相关联的变量。

函数

函数 含义
length(str) 返回str中的字符个数,如果没有带参数,则返回当前记录中的字符个数
int(num) 返回num的整数部分
index(str1, str2) 返回str2在str1中的索引,如果str2不存在就返回0
split(str, arr, del) 用del作为分隔符,将str元素放到数组arr[1]…arr[n]中,返回数组中的元素个数
sprintf(fmt, args) 根据fmt格式化args并返回格式化后的字符串;模仿C语言中的同名函数
substr(str, pos, len) 返回str中从pos开始、长度为len个字符的字符串
tolower(str) 返回str的副本,但是其中的所有大写字母被替换成相应的小写字母
toupper(str) 返回str的副本,但是其中的所有小写字母被替换成相应的大写字母

算数运算符

与C语言相同

关联数组

关联数组是gawk最强大的功能之一。这些数组使用字符串作为索引。在使用关联数组时,用户可以用数值字符串作为索引来模仿传统数组。

可以给关联数组中的某个元素赋值。其语法如下:
array[string] = value
其中,array为数组的名称,string为用户将要赋值的元素在数组中的索引,value为将要赋予该元素的值。

可以将for结构用于关联数组。其语法如下:
for (elem in array) action
其中,for结构遍历数组中的元素时,elem表示接收数组中每个元素的值的变量,array为数组的名称,action为gawk对数组中每个元素所采取的行动。可以在action中使用elem变量。

printf

可以使用printf命令来代替print控制gawk产生的输出的格式。gawk版的printf类似于C语言中的printf。printf命令的语法如下:

printf “control-string”, arg1, arg2, arg3, …, argn

control-string决定printf如何格式化arg1,arg2, …, argn。这些参数既可以是变量也可以是其他表达式。可以在control-string中使用\n来表示换行符,使用\t来表示制表符。control-string包含转换说明,每个参数对应一个表达式。转换格式如下:

%[-][x[.y]] conv

其中,“-”使printf将参数左对齐,x表示最小字段宽度,“.y”表示数字中小数点右边的位数。conc指示数值转换的类型。

conv 转换类型
d 十进制
e 指数表示
f 浮点数字
g 使用f或者e中较短的那个
o 无符号八进制
s 字符串
x 无符号十六进制

控制结构

控制(流)语句将改变gawk程序中命令的执行顺序。

  1. if…else
    语法结构如下:
if (condition)
{ commands }
[else
{ commands }]
  1. while
    语法结构如下:
while (condition)
{ commands }
  1. for
    语法结构如下:
for (init; condition; increment)
{ commands }
  1. break
    break语句将控制权转移到for或者while循环之外,终止它所在的最内层循环执行。

  2. continue
    continue语句将控制权转移到for或者while循环的末尾,使它所在的最内层循环继续执行下一次迭代。

示例

car数据文件

cars文件
plym fury 1970 73 2500
chevy malibu 1999 60 3000
ford mustang 1965 45 10000
volvo s80 1998 102 9850
ford thundbdd 2003 15 10500
chevy malibu 2000 50 2500
bmw 325i 1985 115 450
honda accord 2001 30 6000
ford taurus 2004 10 17000
toyota rav4 2002 180 750
chevy impata 1985 85 1550
ford explor 2003 25 9500

缺失模式

一个简单的gawk程序如下:

{ print }

这个程序由单行程序组成,这行程序为一个动作。因为没有模式,所以gawk选择输入中的所有行。

$ gawk '{ print }' cars

缺失动作

$ gawk '/chevy/' cars
chevy malibu 1999 60 3000
chevy malibu 2000 50 2500
chevy impata 1985 85 1550

字段

下面是没有模式的文件所有行。用花括号将动作括起来。必须使用花括号限定动作,这样gawk才可以将动作与模式部分区分开。示例显示每一行的第3个字段( 3)1( 1)。

$ gawk '{print $3, $1}' cars
1970 plym
1999 chevy
1965 ford
......

下面示例包含模式和动作,它选中包含字符串chevy的所有行并显示选中行的第3个字段和第1个字段:

$ gawk '/chevy/ {print $3,$1}' cars 
1999 chevy
2000 chevy
1985 chevy

下面示例中,gawk选中包含与正则表达式h匹配的行。因为没有显式指定动作,所以gawk显示它选中的所有行。

$ gawk '/h/' cars
chevy malibu 1999 60 3000
ford thundbdd 2003 15 10500
chevy malibu 2000 50 2500
honda accord 2001 30 6000
chevy impata 1985 85 1550

~(匹配运算符)

下面示例中,使用匹配运算符(~)来选择在第1个字段中包含字符h的所有行:

$ gawk '$1 ~ /h/' cars
chevy malibu 1999 60 3000
chevy malibu 2000 50 2500
honda accord 2001 30 6000
chevy impata 1985 85 1550

正则表达式中的脱字符(^)强制在行首进行匹配,在这个示例中,从第1个字段的起始出匹配:

$ gawk '$1 ~ /^h/' cars
honda accord 2001 30 6000

字符两边使用方括号。表示方括号内的任意一个都匹配。

$ gawk '$2 ~ /^[tm]/ {print $3,$2,"$" $5}' cars
1999 malibu $3000
1965 mustang $10000
2003 thundbdd $10500
2000 malibu $2500
2004 taurus $17000

美元符号

美元符号在gawk程序中所起的3中作用。1)、美元符号后面紧跟一个数字来表示某个字段。2)、在正则表达式中,美元符号强制在行尾或者字段末尾($5)进行匹配。3)、在字符串中美元符号代表自身。

$ gawk '$3 ~ /5$/ {print $3, $1 " $" $5}' cars
1965 ford $10000
1985 bmw $450
1985 chevy $1550

gawk使用等于关系运算符(==)对每行的第3个字段与数字1985进行数值比较。

$ gawk '$3==1985' cars
bmw 325i 1985 115 450
chevy impata 1985 85 1550

文本比较

使用文本比较要加引号

$ gawk '"2000"<=$5 && $5<"9000"' cars
plym fury 1970 73 2500
chevy malibu 1999 60 3000
chevy malibu 2000 50 2500
bmw 325i 1985 115 450
honda accord 2001 30 6000
toyota rav4 2002 180 750

,(范围运算符)

范围运算符(,)用来选取一组文本行。选中的第1行是逗号之前的模式所指定的行,选中的最后一行是逗号之后的模式所指定的行。如果没有匹配逗号之后模式的文本行,gawk就选取直到输入末尾额所有行。下面是从包含volvo的行开始到包含bmw的行结束的所有文本行。

$ gawk '/volvo/ , /bmw/' cars
volvo s80 1998 102 9850
ford thundbdd 2003 15 10500
chevy malibu 2000 50 2500
bmw 325i 1985 115 450

–file选项

如果用户正在编写较长的gawk程序,那么可以将程序放在一个文件中,然后在命令行上引用该文件。使用-f(–file)选项,后面紧跟着包含该gawk程序的文件的名称。

BEGIN

$ cat pr_header
BEGIN {print "Make Model Year Miles Price"}
{print}

$ gawk -f pr_header cars
Make Model Year Miles Price
plym fury 1970 73 2500
chevy malibu 1999 60 3000
ford mustang 1965 45 10000
......

END

END模式的工作方式与BEGIN模式类似,但是gawk在处理完输入的最后一行之后才执行与该模式关联的动作。

$ gawk 'END {print NR, "cars for sale. "}' cars
12 cars for sale.

length()函数

如果用户不带参数调用length()函数,那么它将返回当前文本行中的字符个数,包含字段分隔符。$0变量总是包含当前文本行的内容。在下一个示例中,gawk将行长度添加到每行的开头,然后通过管道将输出从gawk发送到sort(-n 指定数值排序)。

$ gawk '{print length, $0}' cars | sort -n
22 bmw 325i 1985 115 450
23 plym fury 1970 73 2500
24 volvo s80 1998 102 9850
25 ford explor 2003 25 9500
25 toyota rav4 2002 180 750
26 chevy impata 1985 85 1550
26 chevy malibu 1999 60 3000
26 chevy malibu 2000 50 2500
26 ford taurus 2004 10 17000
26 honda accord 2001 30 6000
27 ford mustang 1965 45 10000
27 ford thundbdd 2003 15 10500

NR(记录编号)

NR变量包含当前行的记录编号(行号)。下面的模式选取字符数多余24的所有行。动作则显示选中的每一行的行号。

$ gawk 'length > 24 {print NR}' cars 
2
3
5
6
8
9
10
11
12

范围(,)和NR变量可以一起使用,根据行号来显示一组文本行。下面这个示例显示第2~4行之间的行。

$ gawk 'NR==2,NR==4' cars
chevy malibu 1999 60 3000
ford mustang 1965 45 10000
volvo s80 1998 102 9850

OFS变量

可以通过某个值赋予OFS变量来更改输出字段分隔符的值。
下面示例使用反斜杠转义序列\t将制表符赋予OFS。这样就改善了报告的输出格式,但并没有正确的对齐。

$ gawk -f ofs_demo cars
plymouth fury 1970 73 2500
chevrolet malibu 1999 60 3000
ford mustang 1965 45 10000
volvo s80 1998 102 9850
ford thundbdd 2003 15 10500
chevrolet malibu 2000 50 2500
bmw 325i 1985 115 450
honda accord 2001 30 6000
ford taurus 2004 10 17000
toyota rav4 2002 180 750
chevrolet impata 1985 85 1550
ford explor 2003 25 9500

printf

可以使用printf进一步改善输出格式。下面的示例在几个程序行的末尾使用反斜杠转义随后的换行符。可以使用这种方法来续写较长的一行或者多行,而不会影响到程序的输出结果。

$ cat printf_demo 
BEGIN{
print " Miles"
print "Make Mode Year (000) price"
print \
"------------------------------------------------"
}
{
if($1 ~ /ply/) $1 = "plymouth"
if($1 ~ /chev/) $1 = "chevrolet"
printf "%-10s %-8s %-2d %5d $ $ %8.2f\n",\
$1,$2,$3,$4,$5
}

$ gawk -f printf_demo cars
Miles
Make Mode Year (000) price
------------------------------------------------
plymouth fury 1970 73 $ $ 2500.00
chevrolet malibu 1999 60 $ $ 3000.00
ford mustang 1965 45 $ $ 10000.00
volvo s80 1998 102 $ $ 9850.00
ford thundbdd 2003 15 $ $ 10500.00
chevrolet malibu 2000 50 $ $ 2500.00
bmw 325i 1985 115 $ $ 450.00
honda accord 2001 30 $ $ 6000.00
ford taurus 2004 10 $ $ 17000.00
toyota rav4 2002 180 $ $ 750.00
chevrolet impata 1985 85 $ $ 1550.00
ford explor 2003 25 $ $ 9500.00

重定向输出

下面定义了两个文件:一个文件存放包含chevy的所有行;另一个文件存放包含ford的所有行。

$ cat redirect_out
/chevy/ {print > "chevfile"}
/ford/ {print > "fordfile"}
END {print "demo."}

$ gawk -f redirect_out cars
demo.

$ cat chevfile
chevy malibu 1999 60 3000
chevy malibu 2000 50 2500
chevy impata 1985 85 1550

$ cat fordfile
ford mustang 1965 45 10000
ford thundbdd 2003 15 10500
ford taurus 2004 10 17000
ford explor 2003 25 9500

summary程序生成关于所有车和较新车的一个总结报告。

$ cat summary
BEGIN {
yearsum = 0; costsum = 0
newcostsum = 0; newcount = 0
}
{
yearsum += $3
costsum += $5
}
$3 > 2000 { newcostsum += $5; newcount ++ }

END {
printf "Average age of cars is %4.1f years\n",\
2006 - (yearsum/NR)
printf "Average cost of cars is $%7.2f\n",\
costsum/NR
printf "Average cost of newer is $%7.2f\n",\
newcostsum/newcount
}

$ gawk -f summary cars
Average age of cars is 13.1 years
Average cost of cars is $6133.33
Average cost of newer is $8750.00

FS变量

下面这个示例揭示了在某个字段中查找最大数字的技巧。因为它处理linux的password文件,这个文件使用冒号(:)限定各个字段,所以这个示例在读取任何数据之前更改输入字段分隔符(FS)。它读取passwd文件并判断下一个可用的用户ID编号(第3个字段)。这个程序的运行,并不要求在passwd文件中的这些数字有序。

模式($3》> saveit)使gawk选取包含用户ID编号大于任何它前面曾处理过的用户ID编号的记录。每次选中一行,gawk将这个新用户ID编号赋予saveit变量。然后gawk使用saveit这个新值来测试所有后续记录的用户ID。最后gawk将saveit的值加1,并显示该结果:

$ cat find_uid 
BEGIN {FS = ":"
saveit = 0
}
$3 > saveit {saveit = $3}
END {print "Next available UID is " saveit + 1}

$ gawk -f find_uid /etc/passwd
Next available UID is 65535

if-else

$ cat price_range 
{
if($5 <= 5000) $5 = "indexpensive"
else if(5000 < $5 && $5 < 10000) $5 = "please ask"
else if(10000 <= $5) $5 = "expensive"
#
printf "%-10s %-8s %2d %5d %-12s\n",\
$1, $2, $3, $4, $5
}

$ gawk -f price_range cars
plym fury 1970 73 indexpensive
chevy malibu 1999 60 indexpensive
ford mustang 1965 45 expensive
volvo s80 1998 102 please ask
ford thundbdd 2003 15 expensive
chevy malibu 2000 50 indexpensive
bmw 325i 1985 115 indexpensive
honda accord 2001 30 please ask
ford taurus 2004 10 expensive
toyota rav4 2002 180 indexpensive
chevy impata 1985 85 indexpensive
ford explor 2003 25 please ask

关联数组

下面这个manuf关联数组使用cars文件中的每条记录的第1个字段的内容作为索引。这个数组由元素manuf[plym]、manuf[chevy]、manuf[ford]等组成。每个元素在创建时都被初始化为0(零)。运算符++将递增其后的变量。

for结构

END模式之后的动作是为for结构准备的,该for结构循环遍历关联的数组的每个元素。通过管道将输出送到sort,以生成按字母顺序排列的文件和库存量的有序列表。

$ cat price_range
{
if($5 <= 5000) $5 = "indexpensive"
else if(5000 < $5 && $5 < 10000) $5 = "please ask"
else if(10000 <= $5) $5 = "expensive"
#
printf "%-10s %-8s %2d %5d %-12s\n",\
$1, $2, $3, $4, $5
}

$ ./manuf
bmw 1
chevy 3
ford 4
honda 1
plym 1
toyota 1
volvo 1

下面名为manuf.sh的程序是一个更加通用的shell脚本,它包含一些错误检查功能。这个脚本列出文件中某一列的内容并对其进行计数,使用在命令行上指定的列号和文件名。

本篇博客的资料来源于《Linux命令、编辑器与Shell编程》