写在前面:如果此文有幸被某位朋友看见并发现有错的地方,希望批评指正。如有不明白的地方,愿可一起探讨。
awk是个什么东西
最简单地说,AWK是一个用于处理文本的编程语言工具,是linux及UNIX环境中现有的功能最强大的数据处理引擎之一。AWK提供了极其强大的功能:可以进行正则表达式的匹配,样式装入,流控制,数学运算符,程序控制语句以及甚至拥有内置的变量和函数。它具备了一个完整的语言所应该具有的特性,实际上AWK的确拥有自己的语言:AWK程序设计语言,三位创建者(Alfred Aho, Peter Weinberger, and Brian Kernighan)已将其定义为“样式扫描和处理语言”。以上内容引用于维基百科。
AWK基本语法结构及其工作机制
AWK基本语法结构
awk [options] 'program' file file ...
awk [options] 'PATTERN{action}' file file ...
AWK的工作机制
首先根据模式去读取文件的某行或者多行,如果没有给模式匹配,就去读取文件的所有行;
然后将所读取的行以输入分隔符(默认情况下,输入分隔符为空白)进行切片,将所切取的每片赋值给内建变量$0(代表整行),$1(代表第一个字段),$2(代表第二个字段),...;
最后根据语法结构中的action来对各个字段进行相应的处理。
AWK如何处理各行
AWK自身具有遍历功能,其主要作用是遍历文件中的所有行;那么AWK如何处理各行字段呢?那就得用到循环功能了。
接下来,本文将描述基本语法结构中的program和PATTERN{action};而AWK的[options]非常简单,并且可以包含在program和PATTERN{action}中,因此本文将一笔带过。
AWK的options
先感受一下awk的使用方法
-F CHAR:输入分隔符
# awk -F : '{print}' /etc/passwd
# awk -F : '{print $1 $7}' /etc/passwd
# awk -F : '{print "hello"}' /etc/passwd
# awk -F : '{print "hello" $1}' /etc/passwd
# awk -F : '{print 100}' /etc/passwd
AWK的options真的很简单,更多的options建议查看帮助手册
AWK的输出
print item1,item2,...
基本要点:
(1)各项之间使用逗号分隔,而输出时则使用输出分隔符分隔(默认情况下为空白字符),而不是逗号分隔;
(2)输出的各项可以为字符串、数值、当前记录的字段、变量以及awk表达式;注意:数值会被隐式转换为字符串后输出。想想为什么?(显示器本身就是字符设备)。
(3)如果print后面的item被省略,相当于print $0;如果想输出空白,则使用print ""。
AWK的变量
AWK的变量分为内置变量和自定义变量
内置变量:
FS: Field Seperator,输入时的字段分隔符;
# awk 'BEGIN{FS=":"}{print $1,$7}' /etc/passwd
# awk -F : '{print $1,$7}' /etc/passwd
比较以上两条命令的输出结果有何不同?其实没有什么不一样。
这里想说的是:-F CHAR 是选项,与内置变量不是同一概念
RS: Record Seperator,输入时的行分隔符;
# awk 'BEGIN{RS=":"}{print $1,$7}' /etc/passwd
# awk 'BEGIN{FS=":"}{print $1,$7}' /etc/passwd
比较以上两条命令的输出结果有何不同,就可知道行分隔符的概念了,默认情况下为\n。
仔细比较你会发现:
第七字段为空,而执行# awk 'BEGIN{RS=":"}{print $1,$7}' /etc/passwd命令的结果中发现少了一些字段输出,那是因为我们并没有输出换行符接下来的那个字段(不知道它属于哪个字段???)。试一试这条命令:
# awk 'BEGIN{RS=":"}{print $1,$ARGC}' /etc/passwd,查看结果就明白了。
OFS: Output Field Seperator,输出时的字段分隔符;
# awk 'BEGIN{FS=":";OFS=":"}{print $1,$7}' /etc/passwd
注意:定义两个变量时,需要用;分隔开来(语句之间需要分号分割)
# awk 'BEGIN{FS=":";OFS=":"}{print $1 $7}' /etc/passwd
# awk 'BEGIN{FS=":";OFS=":"}{print $1$7}' /etc/passwd
注意比较以上两条命令的执行结果,看看是什么样子
ORS: Outpput Row Seperator, 输出时的行分隔符;
# awk 'BEGIN{FS=":";ORS=":"}{print $1,$7}' /etc/passwd
# awk 'BEGIN{FS=":";ORS="###"}{print $1,$7}' /etc/passwd
NF:Numbers of Field,每行的字段数;
NR:Numbers of Record, 行数;所有文件的一并计数;
此概念比较诡异,存在多个文件时,是单独记数,还是整体记数??所有文件一并记数
FNR:行数;各文件分别计数;
与NR相对应??测试一下就知道
# awk 'BEGIN{FS=":";OFS=":"}{print FNR,$1,$7}' /etc/passwd /etc/group
# awk 'BEGIN{FS=":";OFS=":"}{print NR,$1,$7}' /etc/passwd /etc/group
# awk 'BEGIN{FS=":";OFS=":"}{print FNR,$1,$7}' /etc/passwd
注意,上例中的FNR不能加$,加$表示的是引用字段,测试一下就知道:
# awk 'BEGIN{FS=":";OFS=":"}{print $FNR,$1,$7}' /etc/passwd
ARGV:数组,例如:awk '{print $0}' 1.txt 2.txt,意味着ARGV[0]保存awk;
注意'{print $0}'并不保存,也即ARGV[1]保存1.txt
# awk 'BEGIN{print ARGV[0],ARGV[1]}' /etc/passwd /etc/group
注意print不能大写
ARGC: 保存awk命令中参数的个数;
# awk 'BEGIN{print ARGV[0],ARGV[1],ARGC}' /etc/passwd /etc/group
FILENAME: awk正在处理的当前文件的名称;
# awk 'BEGIN{FS=":";OFS="-->"}{print $1,FILENAME}' /etc/passwd
用户可自定义变量:
-v var_name=VALUE
注意,变量名区分字符大小写;
(1)可以在program中定义变量;
(2)可以在命令行中通过-v选项自定义变量;
# awk 'BEGIN{a="hello awk"}{print a}' /etc/passwd
# awk 'BEGIN{a="hello awk";print a}'
表示不对文件进行处理,因为BEGIN这个模式跟文件没有关系,只是在处理文件前起作用
# awk -v a="hello awk" 'BEGIN{print a}'
与# awk 'BEGIN{a="hello awk";print a}'效果一样
awk的printf命令
如果你学过C语言,你会发现这个printf似曾相识,是不是有种亲切感,^_^。
命令格式:
printf format, item1,item2,...
基本要点:
(1)format需要指定;
(2)format用于为后面的每个item指定其输出格式;
(3)不会自动换行;如需换行需要在format中添加\n;
format格式的指示符都以%开头:
%c: 显示字符的ASCII码;
%d, %i: 十进制整数;
%e, %E: 科学计数法显示数值;
%f: 显示浮点数;
%g, %G: 以科学计数法格式或浮点数格式显示数值;
%s: 显示字符串;
%u: 显示无符号整数;
%%: 显示%自身;
修饰符(输出格式的修饰符):
#:(数字)显示宽度
-:左对齐,默认右对齐
+:显示数值的符号
.#: 取值精度
# awk -F : '{printf "%-20s %-30s\n",$1,$7}' /etc/passwd
# awk -F : '{printf "%-20s, %-30s\n",$1,$7}' /etc/passwd
比较以上两个命令的输出结果,看有什么不同
"%-20s, %-30s\n"中的逗号(,)会原样输出
# awk 'BEGIN{printf "%f\n",3.1415}'
# awk 'BEGIN{printf "%f\n",3.1415926}'
比较以上两个命令的输出结果,看有什么不同
# awk 'BEGIN{printf "%g\n",3.1415926}'
# awk 'BEGIN{printf "%e\n",3.1415926}'
# awk 'BEGIN{printf "%e\n",31415.926}'
比较上面两条命令,看有什么不同,注意点(.)的位置
# awk 'BEGIN{printf "%15e\n",31415.926}'
# awk 'BEGIN{printf "%15.2e\n",31415.926}'
# awk 'BEGIN{printf "%-15.2e\n",31415.926}'
AWK的输出重定向
print items > output-file
print items >> output-file
print items | command
特殊文件描述符:FD-->File Description
进程打开某文件时,内核就会追踪这个文件且会使用很小的数字代表这个文件
标准输入(/dev/stdin): 代码为0,<或<<
标准输出(/dev/stdout): 代码为1,使用1>或1>>
错误输出(/dev/stderr): 代码为2,使用2>或2>>
AWK的操作符
算术操作符:
x+y,x-y,x*y,x/y,x**y,x^y,x%y,-x:负值,+x:转换为数值
字符串操作符:连接
赋值操作符:
=,+=,-=,*=,/=,%=,^=,**=,++,--
如果模式自身是=号,要写为/=/
比较操作符:
<,<=,>,>=,==,!=
~:模式匹配,左边的字符串能够被右边的模式所匹配为真,否则为假;
!~:与~相反
逻辑操作符:
&&: 与
||:或
条件表达式:
selector?if-true-expression:if-false-expression
注意,表达式与语句的区别;一般说来表达式有值,而语句不一定有值。
# awk -F: '{$3>=500?utype="common user":utype="admin or system user";print $1,"is",utype}' /etc/passwd
函数调用:
function_name(argu1,argu2)
AWK的模式
(1) Regexp: 格式为/PATTERN/
仅处理被/PATTERN/匹配到的行;
# awk -F : /root/ '{print $0}' /etc/passwd
# grep 'root' /etc/passwd
注意:如果仅过滤文本,不建议使用awk
(2) Expression: 表达式,其结果为非0或非空字符串时满足条件;
仅处理满足条件的行,一般“比较表达式”居多
# awk -F : '$3>=500{print $1,$3}' /etc/passwd
(3) Ranges: 行范围,此前称为地址定界,startline, endline
仅处理范围内的行;
# awk '/root/,/bin/{print $0}' /etc/passwd /etc/group
(4) BEGIN/END: 特殊模式,仅在awk命令的program运行前(BEGIN)或运行后(END)执行一次;
BEGIN一般写在action之前,END一般写在action后
BEGIN用来定义表头以及变量
END用来定表尾
(5) Empty:空模式,匹配任意行;
AWK的控制语句
8.1 if-else
格式:if (condition) {then body} else {else body}
# awk -F: '{if ($3>=500) {print $1,"is a common user"} else {print $1, "is an admin or system user"}}' /etc/passwd
# awk '{if (NF>=8) {print $0}}' /etc/inittab
8.2 while
格式:while (condition) {while body}
# awk '{i=1; while (i<=NF){printf "%s ",$i;i+=2};print ""}' /etc/inittab
# awk -F : '{i=1; while (i<=NF){printf "%s ",$i;i+=2};print ""}' /etc/passwd
# awk -F : '{i=1; while (i<=NF){printf "%s ",$i;i+=2};}' /etc/passwd
思考,为什么上面两行输出结果的格式有所差别??
# awk '{i=1; while (i<=NF){printf "%s ",$i;i+=2};print "\n"}' /etc/passwd
# awk -F : '{i=1; while (i<=NF){printf "%s ",$i;i+=2};print "\n"}' /etc/passwd
# awk '{i=1; while (i<=NF){if (length($i)>=6) {print $i}; i++}}' /etc/inittab
# awk -F : '{i=1; while (i<=NF){if (length($i)>=6) {print $i}; i++}}' /etc/passwd
length()函数:取字符串的长度
8.3 do-while循环
格式:do {do-while body} while (condition)
8.4 for循环
格式:for (variable assignment; condition; iteration process) {for body}
# awk '{for (i=1;i<=NF;i+=2){printf "%s ",$i};print ""}' /etc/inittab
# awk '{for (i=1;i<=NF;i++){if (length($i)>=6) {print $i}}}' /etc/inittab
for循环可用来遍历数组元素:
语法:for (i in array) {for body}
遍历的不是元素,而是元素的索引
8.5 case语句
语法:switch (expression) {case VALUE or /RGEEXP/: statement1;... default: stementN}
8.6 循环控制
break
continue
8.7 next
提前结束对本行的处理进而进入下一行的处理;
# awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd
# awk -F: '{if(NR%2==0) next; print NR,$1}' /etc/passwd
AWK的数组
关联数组:
array[index-expression]
index-expression: 可以使用任意字符串; 如果某数组元素事先不存在,那么在引用时,awk会自动创建此元素并将其初始化为空串;因此,要判断某数组是否存在某元素,必须使用“index in array”这种格式;
A[first]="hello awk"
print A[second]
要遍历数组中的每一个元素,需要使用如下特殊结构:
for (var in array) {for body}
其var会遍历array的索引,而非元素本身;
state[LISTEN]++
state[ESTABLISHED]++
# netstat -tan | awk '/^tcp/{++state[$NF]}END{for (s in state) {print s,state[s]}}'
# netstat -tan | awk '/^tcp/{++state[$NF]}{for (s in state) {print s,state[s]}}'
比较以上两条命令执行的结果,看有何不同??
# awk '{ip[$1]++}END{for (i in ip) {print i,ip[i]}}' /var/log/httpd/access_log
删除数组元素:
delete array[index]
AWK的内置函数
split(string,array[,fieldsep[,seps]]):
功能:将string表示的字符串以fieldsep为分隔符进行切片,并切片后的结果保存至array为名的数组中;数组下标从1开始;
注意,此函数有返回值,返回值为切片后的元素的个数
root:x:0:0::/root:/bin/bash
user[1]="root", user[2]
# awk 'BEGIN{split("root:x:0:0",user,":");print user[1]}'
# awk 'BEGIN{split("root:x:0:0",user,":");for (i in user) {print user[i]}}'
# awk 'BEGIN{split("root:x:0:0",user,":");for (i in user) {print i,user[i]}}'
注意:存储时,并没有按顺序???为什么总是从第四个地段开始到末尾,然后从第一个地段开始到第三个字段???
# netstat -tn | awk '/^tcp/{lens=split($5,client,":");ip[client[lens-1]]++}END{for (i in ip) print i,ip[i]}'
注意,时刻想着执行某条语句,表达式以及某个函数后,是否有返回值???
length(string)
功能:返回给定字串的长度
substr(string,start[,length])
功能:从string中取子串,从start为起始位置为取length长度的子串;