AWK模式处理语言

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编程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值