The AWK Programming Language
  Pq37jUF4UeqZ 2023年11月02日 73 0
awk

@Author: Basil Guo @Date: Jan. 19, 2021 @Description: The AWK Programming Language @Keyword: Linux, awk @Type: tutorial

《The AWK Programming Language》——Alfred V. Aho, Brian W. Kernighan,and Peter J. Weinberger

本文无意于重新翻译一遍这本作者做的书籍,有关翻译在GitHub上已经有人做了awk_zh_cn,有PDF可以下载。

本文会根据常用的工作取舍书中例子。本文如有后续,会更新新的gawk(GNU awk)的内容。

1. 简介

一个awk程序由“模式+动作”组成,可以简单的修改数据格式、验证数据有效性、搜索特定数据项、求和、打印报表等。“模式”指定搜索哪些数据,“动作”表示对搜索到的数据进行哪些处理。模式可以不存在代表会对每一行都执行后续动作,模式存在则是对匹配行执行后续动作,动作及其{}都可以不存在表示会打印每一个匹配的输入行,但是不能两个都不存在,不然虽然语法通过,但是没有意义。

awk 是针对文本文件逐行处理的,可以应用在多种不同的计算和数据处理任务中,awk会自动将输入行切分成字段,awk缺省的行分隔符是换行,缺省的列分隔符是连续的空格和Tab,但是行分隔符和列分隔符都可以自定义。但是一般使用awk主要是逐列处理数据的,这也是awk相比sed更加强大的地方。

awk 程序基本格式是:awk [OPTIONS] '[PATTERN]{ACTION}' /PATH/TO/FILE,这里必须使用''单引号,以防止其中的$被Shell解释,如果程序长度多于一行,也需要使用单引号。多个模式-动作语句或者动作内语句可以使用;或者换行分隔。

pattern {action} 
[pattern {action}; ... ]			# 我是行注释

可以使用\换行,也可以使用,换行。每一行的字段数可以不尽相同,但是默认将字段数上限设置为100。

emp.data保存了当前一些工人单位工资和工时数据:

现在我们想查看下工时不是0的工人他们的薪酬情况:

$ awk '$3 > 0 {print $1,$2 * $3}' emp.data
Kathy 40
Mark 100
Mary 121
Susie 76.5

这里awk 不会修改源文件的数据,而且这里表明ACTION也支持算术表达式。

如果语法错误了,awk 可以检测出来,书中给的例子使用>>> <<<包裹,但是可能gawk做出了改变。例如:

$ awk -F: '$3 > 500 [print $1,$3}' /etc/passwd
awk: cmd. line:1: $3 > 500 [print $1,$3}
awk: cmd. line:1:          ^ syntax error
awk: cmd. line:1: $3 > 500 [print $1,$3}
awk: cmd. line:1:                      ^ syntax error

2. 选项

  • -f /PATH/TO/FILE指定脚本程序文件,这个文件只包含''内的模式+动作部分,不包含别的;

3. 模式

3.1 分类

模式位于{}之外,有六种基本的模式,其中BEGINEND不能省略后续动作,不与其它模式组合。范围模式不能是其它模式的一部分。

  • BEGIN{statements}表示在第一个输入文件的第一行之前被匹配,通常放在程序开头,可以提供初始化的功能,多个BEGIN会按照出现顺序依次执行。
    • 可用来输出一个标题awk 'BEGIN {printf("Name\t$/h\thours\n"); print} {print}' emp.data
    • 常见用途用于搭配内置变量FS更改输入行被分割位字段的方式,就是更改分割符;
  • END{statements}在最后一个输入文件的最后一行被处理之后匹配,通常放在程序结尾,可用于扫尾工作,多个END会按照出现顺序依次执行。awk '$3>15{emp+=1} END{print emp, "employees worked more than 15 hours."}' emp.data
  • expression{statements}表示每碰到一个使得expression为真(非零非空)的输入行,statements就执行一次;
    • 简单比较:$2 > 0
    • 简单计算:$2*$3 > 100 {printf("$%.2f for %s\n", $2*$3, $1)}
    • 文本比较:$1 == "Susie",包含Susie的行;
  • /regular expression/{statements}正则表达式,/Susie/{print},这里和sed很像;有关正则表达式的具体内容,参见正则表达式
    • expression ~ /regexpr/expression中字符串匹配regexpr模式,/regexpr/就是$0 ~ /regexpr/的简写;
    • expression !~ /regexpr/expression中字符串不匹配regexpr模式,如$1 !~ /C/
  • compound pattern{statements}使用&&(AND), ||(OR), !(NOT)以及括号组合起来形成复杂模式;
    • 模式组合:$2 >= 4 || $3 >= 20
  • pattern1, pattern2{statements}范围模式匹配多个输入行,范围模式不能是其它模式的一部分。
    • /Canada/, /USA/匹配的行从包含Canada的行开始,到包含USA的行结束。如果一直都没有匹配到要么不开始,要么到结束。

3.2 运算符

  1. 关系运算符< <= > >= == !=。数值比较使用相对大小,字符串比较使用字典序。只有在两个比较的对象都是数值时,比较才会以数值的形式进行,否则就是字符串形式的比较。

  2. 算术运算符:+ - * / % ^。最后一个是指数运算符。所有的算术运算符皆使用浮点数。

  3. 赋值运算符:= += -= *= /= %= ^=

  4. 条件表达式:?:

  5. 逻辑运算符:&& || !

  6. 匹配运算符:~ !~。分别是匹配和不匹配,可以指定字段匹配不匹配。

  7. 拼接运算符:没有显示的拼接运算符,通过陆续写出字符串常量, 变量, 数组元素, 函数返回值, 与其他表达式, 就可以创建一 个字符串。

  8. 单目运算符:+ -

  9. 自增和自减运算符:++ --

  10. 括号:用于分组

4. 动作

动作都是包围在{}之内的,动作也可以是一个表达式,例如开篇简介所写的算术运算。对于同一模式的动作可以有多个,位于同一个{}之内,动作之间使用;分隔,[模式+]动作也可以有多个之间只要是可以识别即可。如下两个结果相同:

$ awk 'BEGIN {printf("Name\t$/h\thours\n"); print;}{print $0}' emp.data
$ awk 'BEGIN {printf("Name\t$/h\thours\n"); print;}''{print $0}' emp.data
  • {print}:相当于{print $0},其中$0代表的是整行数据,真正分割的数据是从1开始编号的,打印参数之间使用,逗号分开,这样子默认两个值间会打印一个空格,否则输出的两个值之间就没有空格分隔,print默认是换行符结束;
  • {printf}:相比于{print}的简单快速输出,{printf}可以更多格式输出。它的基本格式是{printf(format, value1, value2,...,valuen)}。如awk '{printf("Total pay for %-6s is $%6.2f\n", $1, $2*$3)}' emp.data
  • 变量:awk可以设置变量,不需要实现声明就可以使用:$3 > 15 {emp = emp+1}
  • 拼接:针对字符串来说,{names=nams $1 " "} END{print names},这里不需要显示初始化字符串names
  • if (expression) statements
  • if (expression) statements else statements
  • while (expression) statements
  • for (expression; expression; expression) statements
  • for (expression in array) statements
  • do statements while (expression)
  • break
  • continue

5. 内建变量

变量 意义 默认值
ARGC 命令行参数的个数
ARGV 命令行参数数组
FILENAME 当前输入文件名
FNR 当前输入文件的记录个数
FS 控制着输入行的字段分割符,如果设置FS为一个非空格字符,就可以使该字符成为字段分割符。 " "
NF 当前记录的字段个数。The Number of Fields in the current input record.
NR 到目前为止读取的记录数量,即行数。The total Number of input Records seen so far.
OFMT 数值的输出格式 %.6g
OFS 输出字段分割符 " "
ORS 输出的记录的分割符 "\n"
RLENGTH 被函数match匹配的字符串的长度
RS 控制着输入行的记录分隔符
RSTART 被函数match匹配的字符串的开始
SUBSEP 下标分割符 "\034"

6. 内建函数

6.1 数学类

6.1.1 算术函数

函数 返回值
atan2(y,x) y/x的反正切值,定义域位于(-π,π)
cos(x) x的余弦值,x以弧度为单位
exp(x) x的指数函数,e^x
int(x) x的整数部分;当x>0时,向0取整
log(x) x的自然对数,以e为底
rand(x) 返回一个随机数r0<=r<1
sin(x) x的正弦值,x以弧度为单位
sqrt(x) x的方根
srand(x) xrand()新的随机数种子

6.2 文本类

  • length():求长度,awk '{print $1, length($1)}' emp.data
  • printf():就类似于C语言中的printf(format, args...)函数;

7. 流程控制

  • if-else

    $ awk '$2 > 6 { n = n + 1; pay = pay + $2} 
    	END{if (n>0) { if (n>0)
    						print n, "employees, total pay is", pay, 
    						"average pay is", pay/n
    					else
    						print "no employees are paid more than $6/hour"
    	}'	emp.data
    
  • while

    # interest1 - compute compound interest: value = amount * (1 + rate) ^ years
    # 	input: 	amount rate years
    #	output:	compounded value at the end of each year
    $ awk '{
    	i = 1
    	while (i <= $3) {
    		printf("\t%.2f\n", $1 * (1 + $2) ^ i)
    		i = i + 1
    	}
    }'
    
  • for

    # interest2 - compute compound interest
    #	input:	amount rate years
    # 	output:	compounded value at the end of each year
    $ awk '{
    	for (i=1; i<=$3; i=i+1)
    		printf("\t%.2f\n", $1 * (1 + $2) ^ i)
    }'
    

8. 数组

逆序输出:

# reverse - print input in reverse order by line
$ awk '{line[NR]=$0}	# remember each input line
END {
  i=NR					# print lines in reverse order
  while (i>0) {
    print line[i]
    i = i-1
  }
}' emp.data

9. 例子

  1. 输入行的总行数

    $ awk 'END{print NR}' /PATH/TO/FILE
    
  2. 打印第10行

    $ awk 'NR == 10' /PATH/TO/FILE
    
  3. 打印每一个输入行的最后一个字段

    $ awk '{print $NF}' /PATH/TO/FILE
    
  4. 打印最后一行的最后一个字段

    $ awk '{field = $NF} END{print field}' /PATH/TO/FILE
    
  5. 打印字数多于4个的输入行

    $ awk 'NF > 4' /PATH/TO/FILE
    
  6. 打印最后一个字段值大于4的行

    $ awk '$NF > 4' /PATH/TO/FILE
    
  7. 打印所有输入行的字段数的总和

    $ awk '{nf = nf + NF} END {printf nf}' /PATH/TO/FILE
    
  8. 打印包含Beth的行的数量

    $ awk '/Beth/ {nlines = nlines + 1}  END {print nlines}' /PATH/TO/FILE
    
  9. 打印具有最大值的第一个字段,以及包含它的行(假设$1总是正数)

    $ awk '$1 > max{max = $1; maxline=$0} END{print max, maxline}' /PATH/TO/FILE
    
  10. 打印至少包含一个字段的行

    $ awk 'NF > 0' /PATH/TO/FILE
    
  11. 打印长度超过80个字符的行

    $ awk 'length($0) > 80' /PATH/TO/FILE
    
  12. 在每一行前加入它的字段数

    $ awk '{print NF, $0}' /PATH/TO/FILE
    
  13. 打印每一行的第一个与第二个字段,但是顺序相反

    $ awk '{print $2, $1}' /PATH/TO/FILE
    
  14. 交换每一行的第1和第二个字段,并打印该行

    $ awk '{tmp = $1; $1 = $2; $2 = tmp; print}' /PATH/TO/FILE
    
  15. 将每一行的第一个字段使用行号代替

    $ awk '{$1 = NR; print}' /PATH/TO/FILE
    
  16. 打印删除了第二个字段后的行

    $ awk '{$2 = ""; print}' /PATH/TO/FILE
    
  17. 将每一行的字段按照逆序打印输出

    $ awk '
    {
      for (i=NF; i>0; i=i-1)
        printf("%s\t", $i)
      printf("\n")
    }
    ' /PATH/TO/FILE
    
  18. 打印每一行的所有字段值之和

    $ awk '
    {
      total = 0
      for (i=1; i<=NF; ++i)
        total = total + $i
      print total
    }' /PATH/TO/FILE
    
  19. 将所有行的所有字段值累加起来

    $ awk '
    {
      for(i=1; i<=NF; ++i)
        sum = sum + $i
    } END{print sum}
    ' /PATH/TO/FILE
    
  20. 将每一行的每个字段用它的绝对值替换

    $ awk '
    {
        for (i=1; i<=NF; ++i)
          if ($i < 0)
            $i = -1 * $i
        print
    }' /PATH/TO/FILE
    
  21. 实现wc的功能:分别输出“行 单词 字符”的个数:

    # +1是因为length()不包含换行符号
    $ awk '{nc=nc+length($0)+1; nw=nw+NF} END{print NR, nw, nc}' /PATH/TO/FILE
    

数据集

  1. emp.data

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

    USSR    8649    275     Asia
    Canada  3852    25      North America
    China   3705    1032    Asia
    USA     3615    237     North America
    Brazil  3286    134     South America
    India   1267    746     Asia
    Mexico  762     78      North America
    France  211     55      Europe
    Japan   144     120     Asia
    Germany 96      61      Europe
    England 94      56      Europe
    
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  Pq37jUF4UeqZ   2023年11月02日   74   0   0 awk
Pq37jUF4UeqZ