awk介绍

一、awk概述

awk是GNU的项目之一,是基于早期unix上的awk程序语言改善而来,所以现在我们在CentOS上用的awk其实是叫gawk。awk的作者这三个人:Aho,Kernighan,Weinberger,awk的命名方式是通过这三个人的名字的首字母而来。因为人们习惯用awk,所以后来干脆把awk创建了一个指向gawk的符号链接。

个人翻译的man手册博文记录,awk 4.0+:
https://blog.youkuaiyun.com/u012271055/article/details/84669343

[root@node1 ~]# which awk
/usr/bin/awk
[root@node1 ~]# ls -l /usr/bin/awk
lrwxrwxrwx. 1 root root 4 Oct 16 19:05 /usr/bin/awk -> gawk
[root@node1 ~]# awk -V
GNU Awk 4.0.2
Copyright (C) 1989, 1991-2012 Free Software Foundation.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.

awk是linux文本三剑客中的比较重要的一个成员,linux文本三剑客:
sed,awk,grep
gawk手册的官网链接:
https://www.gnu.org/software/gawk/manual/gawk.html

二、awk工作原理和流程简析

gawk - pattern scanning and processing language
gawk(下文我们说的awk就是gawk),是模式扫描和处理语言(对文本基于模式做扫描,然后基于模式匹配的段,然后做对应的处理)。根据awk的功能,我们有时候把它称为"报告生成器"以及"格式化文本输出工具"。

awk作用:
(1) 维护小型的、个人的数据库;
(2) 生成报告;
(3) 校验数据;
(4) 生成索引以及执行其他文档预备任务;
(5) 使用awk的规则实现你熟悉的其他计算机语言的法则。

gawk会提供以下基础功能:
(1) 按位提取待处理的部分数据;
(2) 对数据进行排序;
(3) 执行简单的网络通信工作;
(4) 可以自己配置和调试awk程序;
(5) 使用C和C++语言来编写awk的扩展函数;

总之:awk相较于grep和sed,其功能更加强大,可以算是一门独立的编程语言。

awk程序由pattern-action(模式-处理动作)以及可选的函数定义语句构成,语法结构大概如下:

@include "filename" pattern { action statements }
function name(parameter list) { statements }
#@include可以引入在filename中定义的模式和语句。

而我们经常打交道的awk核心语法部分应该是类似于如此:

pattern1{action1} ; pattern2{action2};...patternN{actionN} 

上面这个核心部分的东西,属于awk的程序(program)的一部分;

awk执行过程(只包括比较核心的部分,其他不常用的没有给出)大概如下:
(1) 首先,执行通过awk的-v选项的变量赋值;
(2) 接下来,awk会把awk的所有的program编译成awk内部识别的形式;(会有一些内建变量的赋值),awk把输入的文本流按照RS的值分隔成指定文本段,称为Records(记录),以及保存在$0内建变量的内存区域中,然后awk把记录根据NF的值分隔为一个一个的Fields(域,字段),分别保存在内建变量$1、$2、$3、…的内存区域中,与$0是分开存储的。当然还有其他操作,远不止这些;
(3) 然后,如果awk的program中包含BEGIN语句块,就会执行这部分的语句块;
(4) 然后,会去读取内建变量ARGV(一个数组,保存的是每个命令行的非选项参数)中的值,最后一个元素为ARGV[ARGC-1],范围为ARGV[0]~ARGV[ARGC-1];(没有指定文件的话,会去读取标准输入)
(5) 对于每个输入的记录,gawk会去测试是否能被awk程序中的任何模式所匹配到。对于能被awk模式所匹配到的记录,模式对应的动作将会被执行。gawk去测试模式的顺序是它们出现在程序中的顺序;
(6) 当所有文本的输入记录被处理完后,如果还有END代码块的部分,会执行这部分的内容。

以上流程只是简单的概述,其实中间的处理逻辑非常复杂。比如处理单个文件和处理多个文件,FILENAME变量,NR变量,FNR变量,NF变量,OFS变量,ORS变量等的设置,处理逻辑非常复杂。将在下文简单阐述这些内建(预定义)变量的设置和值的改变影响的因为等。

三、awk命令行选项

1、-f program-file 或 --file program-file
指定AWK程序读取的处理程序从文件program-file中读取,而不是从第一个命令行参数。可以是用多个-f或–file选项指定多个文件;简单看看一下的案例:

[root@node1 ~]# cat file1 
BEGIN{print "hello,begin codes"}
[root@node1 ~]# cat file2 
BEGIN{print "xixihaha"}
[root@node1 ~]# awk -f /root/file1 -f /root/file2   #指定读取-f指定的文件中的program
hello,begin codes
xixihaha
[root@node1 ~]# awk '@include "/root/file1";BEGIN{print "my name is xiaotang."}'
hello,begin codes
my name is xiaotang.
#如果要混用文件中的program和命令行的program,可以使用@include指令载入包含program的文件。

2、-F fs或–field-separator fs
使用fs作为输入域分隔(符) (FS是预先定义的变量,即AWK内置变量之一)
默认如果不指定FS变量以及-F选项,会使用默认行为的"空白"作为输入记录的字段分隔符。
请看示例:

[root@node1 ~]# awk '!/^#/{print $1,$2}' /etc/fstab  #使用默认的空白作为字段分隔符,这里看不懂!/^#/请忽略,这是一个正则表达式的模式(pattern)。
 
/dev/mapper/centos-root /
UUID=a73ff310-545f-46ba-910d-2acba6515f2f /boot
/dev/mapper/centos-swap swap
[root@node1 ~]# tail -2 /etc/passwd
redis:x:996:994:Redis Database Server:/var/lib/redis:/sbin/nologin
geoclue:x:995:993:User for geoclue:/var/lib/geoclue:/sbin/nologin
[root@node1 ~]# tail -2 /etc/passwd | awk -F':' '{print $1,$3}'   #指定字段分隔符为冒号(:)
redis 996
geoclue 995

3、-v var=val 或 --assign var=val
在awk程序执行开始前,把值val复制给变量var。这样的变量赋值形式可以在AWK的BEGIN块中进行。请看示例:

[root@node1 ~]# awk -v a=hello -v b=world 'BEGIN{print a,b}' #通过-v选项赋值
hello world
[root@node1 ~]# awk 'BEGIN{a="hello";b="xiaotang";print a,b}'  #通过BEGIN语句中赋值
hello xiaotang

4、-c或–traditional
运行在兼容模式。运行与兼容模式的gawk,它的行为和传统unix上的awk是一致的,在兼容模式下,GNU特定扩展选项不再支持。下面会有关于GNU扩展的介绍信息。

5、-P或–posix
这个(选项)启用兼容模式,会有以下附加的限制:
(1) 无法识别 \x 的转移;
(2) 当FS被设置为单个空白时,只有空格和Tab充当字段分隔符,换行符不被设置为分隔符;
(3) 之后行不能有? 和 :
(4) 关键字function的同义词func不能被识别;
(5) ** 和 **=操作符不能代替^和^=;

6、-r或–re-interval
启用正则表达式中的区间表达式的使用。(区间表达式非常强大,sed支持)。下面会讲解正则表达式。区间表达式在传统的AWK语言中是不可以用的。POSIX标准新增来它们,使得awk和egrep以及其他保持一致,就是都支持。默认是启用这种特性的,使用–traditional,这种选项会保留。
示例:

[root@node1 ~]# cat /etc/redhat-release 
CentOS Linux release 7.5.1804 (Core) 
[root@node1 ~]# cat txt1
a b c hello
c:123:acd:aa
c:123:acd:aaa
c:123:acd:aaaa
c:123:acd:aaaaa
[root@node1 ~]# awk --traditional '/:a{3,4}$/' txt1   #显式指明--traditional才不会识别区间正则表达式语法
[root@node1 ~]#  awk '/:a{3,4}$/' txt1   #默认CentOS 7启用支持了这种特性,
c:123:acd:aaa
c:123:acd:aaaa

[root@vir-rs2 ~]# cat /etc/redhat-release 
CentOS release 6.9 (Final)
[root@vir-rs2 ~]# cat txt1 
a b c hello
c:123:acd:aa
c:123:acd:aaa
c:123:acd:aaaa
c:123:acd:aaaaa
[root@vir-rs2 ~]# awk '/:a{3,4}$/' txt1    #默认在CentOS 6和以前,这里如果用到区间表达式,默认是不是别的
[root@vir-rs2 ~]# awk --re-interval '/:a{3,4}$/' txt1   #显示加上--re-interval,也不像CentOS 7后awk版本一样,支持-r短选项
c:123:acd:aaa
c:123:acd:aaaa
[root@vir-rs2 ~]# awk --posix '/:a{3,4}$/' txt1  
c:123:acd:aaa
c:123:acd:aaaa

7、-h或–help
以简短格式打印可用选项的摘要,总览信息到标准输出。(根据GUN 编码标准,这个选项会立即成功退出。)
PS:直接使用awk -h会提示我给一个参数(后边随便给个什么参数都行),否则退出状态不成功,帮助信息还是会显示;

8、-V或–version
打印AWK的版本信息到标准输出。

三、awk的变量、记录、域(字段)

3.1、awk的records(记录)

默认情况下,记录是被换行符分隔的(也就是每一行文本是一个记录)。可以通过内建变量RS来控制"(输入)记录"如何被分隔; 如果RS是任意单个字符,那么就用这个单个字符来分隔记录。否则,RS可以作为一个正则表达式。输入文本如果匹配到这个正则表达式,就按照正则表达式来分隔记录。但是,在兼容模式下,只有字符串中的第一个字符会作为记录分隔符。
如果RS设置成null串,记录会被空白分隔。当RS被设置为空串(null)的时候(RS=’ '),不管内置变量FS设置成什么,换行符(\n)总会被作为一个域分隔符。请看以下这张图:
在这里插入图片描述

文件在物理硬盘里存储上表现的形式无非是存储在磁盘的数据块上,数据块取出,组织成文件系统文件的形式,可以理解为文本数据都是文本流的形式(对文件操作有专门的I/O系统调用)。换行符属于控制字符,默认awk就是按照这个换行符来做分隔,就好像awk是按照一行行文本来处理的一样,实际可以通过改变RS的值来改变这种行为。换行符也是一个字符,在标准输出屏幕或者通过文本工具查看,都会换行显式。实际可以理解为,比如"axxdddfafas\nasdfasdljfkajskl\n"这种形式。请看示例:

[root@vir-rs2 ~]# cat 123.txt 
abcd 1234:3456:AXC
xiaotang is not xitang:she was a xxx
12345ajltup:12
[root@vir-rs2 ~]# awk '{print}' 123.txt 
abcd 1234:3456:AXC
xiaotang is not xitang:she was a xxx
12345ajltup:12
[root@vir-rs2 ~]# awk 'BEGIN{RS=":"}{print}' 123.txt    #在BEGIN中,改变RS的值
abcd 1234       #这一这里的变化,是按照冒号对记录做了分隔,默认的{print}动作就是打印$0的值;
3456
AXC
xiaotang is not xitang
she was a xxx
12345ajltup
12

[root@vir-rs2 ~]# awk 'BEGIN{RS=" "}{print}' 123.txt  #指定RS为空
abcd
1234:3456:AXC 
xiaotang
is
not
xitang:she
was
a
xxx
12345ajltup:12

3.2、awk的fields(域,字段)

当每个输入记录被读的时候,gawk使用内建变量FS的值作为域分隔符,会把记录分隔成域的形式,通常也叫分隔的多个字段。如果FS是一个单独的字符,字段会被这个单独的字符所分隔(把输入记录按照这个单独的字符分隔成一个一个的字段)。如果FS是一个空串(null string),那么每个单独的字符都是一个单独的字段。其他形式,FS可以是一个完整的正则表达式。
输入记录中的每个域名可以通过它的位置所引用,$1,$2等等。$0表示全部记录。域不需要被常量所引用(简单来说不能把保存字段数据的变量$1,$2,$3…赋值给一个常量,例如这种形式是有语法错误的:{1=$1} )。

(内置)变量NF记录了输入记录中域的总数;
PS:小技巧,引用输入记录的最后一个字段的值,$NF;引用输入记录倒数第二个字段的值,$(NF-1),注意要使用小括号,依次类推。遍历字段可以使用for循环语句。

深入理解部分:(有些人初学可能不理解,要结合实例来学习)
引用不存在的域(例如:$NF之后的域)会产生一个空串。(简单来说就是,如果你输入记录按照域分隔符分隔后只有3列,你引用$4,$5等就为空串值。)
然后,对不存在的域赋值(例如 $(NF+2)=5 )会增加NF的值;空字符串作为中间域的值,根据OFS值,$0会被重新计算,引用负编号的域是无效的,会导致一个语法错;减少NF值时,字段编号大于NF的域将会丢失。同时$0也会根据OFS重新被计算。对当前存在的域值进行赋值,会使记录在$0被引用时重构。类似地,对$0赋值,也会使记录重新分隔,对域重新赋值。

这部分比较复杂,特别是后半部分,也是我们理解awk工作逻辑的关键所在。我们结合图简单说明,并且结合示例对前半段以及后半段进行解析,请看下图:
在这里插入图片描述

上图中,文本被awk读入之后,初始化后,按照RS指定的输入记录分隔符分隔成一个一个的记录,默认是一行一行,都保存在一个叫做$0的内建变量中。$0有单独的内存区域存储数据,是第一次记录分隔的时候就保存了的。而后,awk为了继续工作,会按照内建变量FS指定的输入域(字段)分隔符分隔输入记录成一个一个的字段,而这些字段总数会记录到一个NF的内建变量中,而每个字段可以使用$符号来引用,比如引用记录的第一个字段为$1,引用记录的第二个字段为$2依次类推。很容易想到,引用记录的最后一个字段是$NF,引用记录的倒数第二个字段是$(NF-1),以此类推。这些单独存储字段记录的变量$1,$2等与$0存储的不是同一个内存区域。
请看示例:

#取/etc/passwd的两行文本,然后给awk处理,awk指定输入字段分隔符为冒号,然后使用默认的输入记录分割符换行符,
#并且打印这两行文本以冒号分隔的第一个字段和第三个字段(用户名和uid)
[root@vir-rs2 ~]# tail -n 2 /etc/passwd | awk 'BEGIN{FS=":"}{print $1,$3}'   
sshd 74
ntp 38

#直接打印整个记录的值,引用 $0
[root@vir-rs2 ~]# tail -n 2 /etc/passwd | awk 'BEGIN{FS=":"}{print $0}'
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin

#引用$NF,打印记录的最后一个字段的值。
[root@vir-rs2 ~]# tail -n 2 /etc/passwd | awk 'BEGIN{FS=":"}{print $NF}'
/sbin/nologin
/sbin/nologin
#利用算术元素,把记录字段数的变量减去1,然后引用$(NF-1)就是倒数第二个字段的值
[root@vir-rs2 ~]# tail -n 2 /etc/passwd | awk 'BEGIN{FS=":"}{print $(NF-1)}'
/var/empty/sshd
/etc/ntp

#利用for循环遍历每个记录(默认是每行)所有的字段,然后打印分别打印每个字段的值。(如果看不懂,可以先往下看)
[root@vir-rs2 ~]# tail -2 /etc/passwd | awk -F':' '{for(i=1;i<=NF;i++){print $i}}'
sshd
x
74
74
Privilege-separated SSH
/var/empty/sshd
/sbin/nologin
ntp
x
38
38

/etc/ntp
/sbin/nologin
[root@vir-rs2 ~]# tail -2 /etc/passwd
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin

#这是让格式更好看,我这里还可以调整格式,不过这里就凑合看一下吧。for循环遍历输入记录的每个字段
[root@vir-rs2 ~]# tail -2 /etc/passwd | awk -F':' '{for(i=1;i<=NF;i++){printf"%-18s",$i};print xxoo}' 
sshd              x                 74                74                Privilege-separated SSH/var/empty/sshd   /sbin/nologin     
ntp               x                 38                38                                  /etc/ntp          /sbin/nologin 


深入理解示例:
摘抄自:https://blog.youkuaiyun.com/anljf/article/details/6433498
1、$1=$1引起的$0根据OFS的值重构$0
[root@vir-rs2 ~]#  echo "aaa bbb"|awk 'BEGIN{OFS="#"}{print $0}' 
aaa bbb
[root@vir-rs2 ~]#  echo "aaa bbb"|awk 'BEGIN{OFS="#"}{print $1,$2}' 
aaa#bbb
说明:我们想通过OFS设置非默认的值(默认值为空格)来使得输出到标准输出不同字段之间的输出分隔不再是空格,可以看到
如果我们直接引用$0,实际是没有效果的。不过可以一个字段一个字段的列出来,就像$1,$2,$3这样,但是这样会有一个问题,
如果我有11个字段,你就要写11个变量,显然,这样不符合"偷懒思维法则"。可以考虑一下for循环如何实现。

[root@node1 ~]# echo|awk '{$1=$1;print NF}'   #NF为空,$1不存在,对不存在的域赋值一次,NF值加1
1
[root@node1 ~]# echo|awk '{$1=$1;$2=$2;print NF}' #对不存在的域名赋值了两次,NF值变成了2
2
[root@node1 ~]# echo| awk '{print NF}' #NF初始值为0,下面会讲解变量引用,会解释默认什么时候是0,什么时候是空串
0
[root@vir-rs2 ~]# echo "aaa bbb ccc ddd eee fff ggg hhh iii"|awk 'BEGIN{OFS="#"}{print $0}'
aaa bbb ccc ddd eee fff ggg hhh iii
[root@vir-rs2 ~]# echo "aaa bbb ccc ddd eee fff ggg hhh iii"|awk 'BEGIN{OFS="#"}{print $1,$2,$3,$4,$5,$6,$7}'
aaa#bbb#ccc#ddd#eee#fff#ggg
现在,我们就想直接使用$0,然后又想使用OFS指定的值来分割,这个时候就要采取一定的策略,然$0的值重构,重构的时候去重新
读取OFS的值对字段进行分隔。比如$1=$1,如果NF不为0,就表示至少$1是存在的,那么适用于法则"对当前存在的域值进行赋值,
会使记录在$0被引用时重构".如果NF为0,表示$1不存在,对不存在的值赋值,适用于法则"对不存在的域赋值(例如 $(NF+2)=5  )会增加NF的值,NF首先为0,如果增加,增加后的值为1"。
[root@vir-rs2 ~]# echo "aaa bbb ccc ddd eee fff ggg hhh iii"|awk 'BEGIN{OFS="#"}{$1=$1;print $0}'
aaa#bbb#ccc#ddd#eee#fff#ggg#hhh#iii
#示例中NF是不为0的,$1存在,对$1这个存在的域赋值,赋值的值为其本身的值,所以输出结果中$1字段的值也不会概念,不过
这样做后,触发了上面提到的$0重构的法则,那么重构其实简单理解就是根据内置变量OFS的值进行分隔$0原先的每个字段的值。
即从"aaa bbb ccc ddd eee fff ggg hhh iii"变成了"aaaaOFSbbbOFScccOFSdddOFSeeeOFSfffOFSgggOFShhhOFSiii",最终打印
结果时,ORS替换成定义的#,所以结果就成了上面的结果,也同样实现了我们需要的结果。

2、$0=$0引起的$0根据FS的值进行分割
[root@node1 ~]# echo "aa#a b#bb"|awk '{print $1}' #默认首先分隔符为空白,所以记录中的第一个字段的值为aa#a
aa#a
[root@node1 ~]# echo "aa#a b#bb"|awk '{print $2}'
b#bb
 #FS重新设置了字段分隔符的值为#,$0=$0表示对$0进行赋值,触发了$0根据FS值重新分隔法则,此时的字段分隔符为#,所以
 $0中的值应该是这样的(重新分割后),aaFSa bFSbb,所以第一个字段为aa,第二个字段为"a b",第三个字段为bb
[root@node1 ~]# echo "aa#a b#bb"|awk '{FS="#";$0=$0;print $0}' 
aa#a b#bb
[root@node1 ~]# echo "aa#a b#bb"|awk '{FS="#";$0=$0;print $1}'
aa
[root@node1 ~]# echo "aa#a b#bb"|awk '{FS="#";$0=$0;print $2}'
a b
[root@node1 ~]# echo "aa#a b#bb"|awk '{FS="#";$0=$0;print $3}'
bb

3.3、awk的变量概述

awk的变量都是动态的,在第一次使用的时候就存在了,不用申明变量类型(属于弱类型语言)。根据变量的使用场景,变量的值可以是浮点数,字符串或者二者兼有。awk变量包括内建变量(预定义变量)和自定义变量。其中内建变量是为了帮助awk更好的完成处理,我们平常可以引用这些内建变量或者适当的调整或利用这些变量,会让awk处理变得更加复杂而且强大。(上一小节,我们就结合几个例子简单的利用了awk的内建变量,并且完成了复杂的功能)。至于内建变量,因为比较多,请看下一小节。这里我们主要来说说自定义变量,自定义变量的方式有两种,分别是:(1) 通过awk的-v选项,在处理程序开始前定义;(2) 在awk的program中定义(其中有包括在BEGIN特殊语句块中定义以及在非特殊语句块中定义),请看下面的示例:

(1) 通过-v选项赋值
[root@node1 ~]# awk -v a=hello -v b=world 'BEGIN{print a,b}'
hello world
(2) 通过在awk的program中赋值
[root@node1 ~]# awk 'BEGIN{myname="xiaotang"}BEGIN{print myname}'
xiaotang
[root@node1 ~]# awk 'BEGIN{myname="hexiaotong";print myname}'
hexiaotong

3.4、awk的内建变量

内建变量的列表如下:

内建变量字符串表示形式
内建含义概述
ARGC命令行参数数量
ARGIND被处理文件对应ARGV数组中的索引值
ARGV命令行参数值的数组。数组索引从0到ARGC-1 。ARGV内容动态变化能够控制提供数据(处理输入记录)的文件。
BINMODE在非POSIX系统,指定使用"二进制"模式处理所有的文件I/O。
在二进制的模式下,数字1,2,3分别指定为输入文件,输出文件和所有文件,
而字符串"r",“w”,"rw"或"wr"分别表示输出文件,输出文件,以及所有文件。
其他字符串的值统一当作"rw"处理(即打开所有文件),不过会产生一个警告消息。
CONVFMT数值的输出格式。
ENVIRON包含当前环境变量的数组。这个数组是以当前环境变量作为索引的,每个元素对应其变量的值。
ERRNO内置变量ERRNO会记录错误的字符串信息。
FIELDWIDTHS空格分隔的字段宽度列表。如果设置来,gawk会显示输入记录的字段成固定的宽度而不是
使用FS的值作为字段分隔符
FILENAME当前输入的文件名。如果在命令行没有文件制定,内建变量FILENAME的值为"-"。
然后,在BEGIN这个特殊的语句块中,FILENAME是未定义的(除非使用了getline)
FNR当前输入文件的输入记录编号
FPAT用于描述记录中字段内容的正则表达式
FS输入字段分隔符,默认是一个空白
IGNORECASEIGNORECASE内置变量控制所有正则表达式和字符串操作过程中的大小写敏感控制
LINT从一个AWK程序内部提供对–lint选项的动态控制
NF当前输入记录的字段的数量
NR目前为止,输入记录的总编号
OFMT默认关于输出格式的格式控制为"%.6g"
OFS输出字段分隔服,默认是空白。
ORS输出记录分隔符,默认是一个换行符
PROCINFO内建变量PROCINFO是一个关联数组
RS输入记录分隔符,默认是换行符。
RT内建变量RT记录着记录终止符。设置后,每次读取一条记录,读取一条记录后终止。
RSTART被match()函数所匹配的第一个字符的索引,如果没有匹配,设置为0
RLENGTH被match()函数所匹配到的字符串的长度,没有匹配,值为-1。
SUBSEP分隔数组元素多个下标的字符,默认为"\034"
TEXTDOMAINAWK程序的文本域说明;用于查找程序字符串的本地环境翻译。

关于更加详细的描述,翻译了一份中英对照的man手册(仅供参考,学术有限,个人理解):

   Built-in Variables  #(gawk)内建变量
       Gawk's built-in variables are:
	   Gawk的内建变量如下:

       ARGC	   The number of command line arguments (does not include options to gawk, or the program source).
	           命令行参数数量(不包括gawk的选项或程序源码)。
		

       ARGIND	   The index in ARGV of the current file being processed.
				   被处理文件对应ARGV数组中的索引值。

       ARGV	   Array of command line arguments.  The array is indexed from 0 to ARGC - 1.  Dynamically changing the	 contents
		   of ARGV can control the files used for data.
		   命令行参数值的数组。数组索引从0到ARGC-1 。ARGV内容动态变化能够控制提供数据(处理输入记录)的文件。
			

       BINMODE	   On  non-POSIX systems, specifies use of “binary” mode for all file I/O.  Numeric values of 1, 2, or 3, specify
		   that input files, output files, or all files, respectively, should use binary I/O.  String values of	 "r",  or
		   "w"	specify that input files, or output files, respectively, should use binary I/O.	 String values of "rw" or
		   "wr" specify that all files should use binary I/O.  Any other string value is treated as "rw", but generates a
		   warning message.
		   
		   在非POSIX系统,指定使用"二进制"模式处理所有的文件I/O。 
		   
		   在二进制的模式下,数字1,2,3分别指定为输入文件,输出文件和所有文件,而字符串"r","w","rw"或"wr"分别表示输出文件,输出文件,以及
		   所有文件。其他字符串的值统一当作"rw"处理(即打开所有文件),不过会产生一个警告消息。
		   

       CONVFMT	   The conversion format for numbers, "%.6g", by default.
					数值的输出格式。

       ENVIRON	   An array containing the values of the current environment.  The array is indexed by the environment variables,
		   each element being the value of that variable (e.g., ENVIRON["HOME"] might be  /home/arnold).   Changing  this
		   array does not affect the environment seen by programs which gawk spawns via redirection or the system() func‐
		   tion.
           包含当前环境变量的数组。这个数组是以当前环境变量作为索引的,每个元素对应其变量的值。例如:ENVIRON["HOME"]表示所处环境的环境变量
           HOME的值。修改这个数组的值不影响直接程序所看到的环境变量。		   
		   
		   
       ERRNO	   If a system error occurs either doing a redirection for getline, during  a  read  for  getline,  or	during	a
		   close(),  then  ERRNO will contain a string describing the error.  The value is subject to translation in non-
		   English locales. 
		   
		   如果在做getline重定向的时候,或则在通过getline读的时候,或者通过close()函数操作的时候,系统出现错误。
		   内置变量ERRNO会记录错误的字符串信息。这个字段记录的值会语言环境的变化,翻译成对应的记录。
		   
	   

       FIELDWIDTHS     A whitespace separated list of field widths.	 When set, gawk parses the input  into	fields	of  fixed  width,
		   instead of using the value of the FS variable as the field separator.  See Fields, above.
		    空格分隔的字段宽度列表。如果设置来,gawk会显示输入记录的字段成固定的宽度而不是使用FS的值作为字段分隔符。(具体FS字段分隔符,
			请看上面注解)
		   

       FILENAME	   The	name  of the current input file.  If no files are specified on the command line, the value of FILENAME is
		   “-”.	 However, FILENAME is undefined inside the BEGIN block (unless set by getline).
		   当前输入的文件名。如果在命令行没有文件制定,内建变量FILENAME的值为"-"。然后,在BEGIN这个特殊的语句块中,FILENAME是未定义的(除非使用了getline)
		   

       FNR	   The input record number in the current input file.
			   当前输入文件的输入记录编号;	
	   

       FPAT	   A regular expression describing the contents of the fields in a record.  When set, gawk parses the input  into
		   fields,  where  the	fields match the regular expression, instead of using the value of the FS variable as the
		   field separator.  See Fields, above.
		   用于描述记录中字段内容的正则表达式。如果设置来,gawk会吧输入记录按照匹配正则表达式来分隔字段值,而不是使用默认的FS变量的
		   值作为字段分隔符。(上面有讲解何为字段(Fields) )
		   
		   参考:https://www.cnblogs.com/yangfengtao/archive/2013/06/07/3124100.html
		   PS:假设有个文本内容为:
		   Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA
		   现在要以逗号为分隔符,取出"1234 A Pretty Street, NE",而这个元素中又有逗号,所以有问题,可以使用FPAT作为字段风格符,
		   ,每个域或者是不包含","的字符串,或者是由一对双引号引起来的字符串。其正则表达式形式如下:
		   FPAT = "([^,]+)|(\"[^\"]+\")"
		   echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA'|awk 'BEGIN{FPAT = "([^,]+)|(\"[^\"]+\")"}{print $3}'
		   
		   
		   

       FS	   The input field separator, a space by default.  See Fields, above.
			   输入字段分隔符,默认是一个空白。关于字段,上面有介绍
	               
	   

       IGNORECASE  Controls the case-sensitivity of all regular expression and string operations.  If IGNORECASE has  a	 non-zero
		   value,  then	 string comparisons and pattern matching in rules, field splitting with FS and FPAT, record sepa‐
		   rating with RS, regular expression matching with ~ and !~, and the gensub(), gsub(),	 index(),  match(),  pat‐
		   split(),  split(),  and  sub()  built-in  functions	all ignore case when doing regular expression operations.
		   
		   NOTE: Array subscripting is not affected.  However, the asort() and asorti() functions are affected.
		   Thus, if IGNORECASE is not equal to zero, /aB/ matches all of the strings "ab", "aB", "Ab", and "AB".  As with
		   all	AWK  variables,	 the initial value of IGNORECASE is zero, so all regular expression and string operations
		   are normally case-sensitive.

		   IGNORECASE内置变量控制所有正则表达式和字符串操作过程中的大小写敏感控制。
		   如果IGNORECASE设置为一个非0值,那么字符串比较,模式匹配规则,使用FS和FPAT内置变量分隔字段,使用内置变量RS分隔
		   记录,通过符号~或~!匹配正则表达式以及gensub(),gsub(),index(),match(),pat-split(),split(),sub()等内建函数,所有
		   上述提到的操作中都不区分字符串大小写(字符串大小写不敏感)。
		   
		   说明: awk中的数组下标不受到内建变量IGNORECASE的影响。然而,asort(),asorti()函数受其影响(默认awk关联数组排序规则
		   和我们认为显示的不一样,有时候要用到asort()和asorti()函数进行排序处理)。
		   如果IGNORECASE设置为非0值,那么/aB/可以匹配"ab","aB","Ab","AB";
		   与所有AWK的变量一样,IGNORECASE的初始值为0,所以所有的正则表达式以及字符串操作全部是大小写敏感的(默认行为)。
		   
		   
		   
       LINT	   Provides dynamic control of the --lint option from within an AWK program.  When true, gawk prints  lint  warnings.
		    When	false,	it  does not.  When assigned the string value "fatal", lint warnings become fatal errors,
		   exactly like --lint=fatal.  Any other true value just prints warnings.
		  
			从一个AWK程序内部提供对--lint选项的动态控制。如果LINT值为真,gawk会打印lint警告,如果设置为假,则不打印。
			如果设置LINT的值为字符串"fatal",lint警告信息将会变成语法错误,和--lint=fatal效果一样。设置为其它真值只会
			打印警告信息。(要了解LINT内建变量,就要了解--lint命令行选项的用法和含义,用的比较少)

		  
	  	   

       NF	   The number of fields in the current input record.
			   当前输入记录的字段的数量。

       NR	   The total number of input records seen so far.
				目前为止,输入记录的总编号。

       OFMT	   The output format for numbers, "%.6g", by default.
				默认关于输出格式的格式控制为"%.6g"。
	   
	   

       OFS	   The output field separator, a space by default.
			  输出字段分隔服,默认是空白。

       ORS	   The output record separator, by default a newline.
				输出记录分隔符,默认是一个换行符。

       PROCINFO	   The elements of this array provide access to information about the running  AWK  program.   On  some	 systems,
		   there may be elements in the array, "group1" through "groupn" for some n, which is the number of supplementary
		   groups that the process has.	 Use the in operator to test for these	elements.   The	 following  elements  are
		   guaranteed to be available:
		   
	   内建变量PROCINFO是一个关联数组。数组中的元素提供了正在运行中的AWK程序的一个访问入口。在某些系统中,还有一些其他元素,
	   从group1到groupn表示n个元素。可以利用关键字in来测试是否这些元素存在(判断数组中元素是否存在,in,下文有讲解)。
	   下面的元素保证都可以使用:
		   

		   PROCINFO["egid"]    the value of the getegid(2) system call.
		   记录系统调用getegid()的值;

		   PROCINFO["strftime"]
				       The default time format string for strftime().
					  默认的时间格式的字符串,相关格式可以了解函数strftime();

		   PROCINFO["euid"]    the value of the geteuid(2) system call.
		   记录系统调用geteuid()的值;

		   PROCINFO["FS"]      "FS"  if	 field	splitting with FS is in effect, "FPAT" if field splitting with FPAT is in
				       effect, or "FIELDWIDTHS" if field splitting with FIELDWIDTHS is in effect.

		   PROCINFO["gid"]     the value of the getgid(2) system call.
		   记录系统调用getgid()的值。

		   PROCINFO["pgrpid"]  the process group ID of the current process.
		   当前进程的运行用户组的组id;

		   PROCINFO["pid"]     the process ID of the current process.
		   当前进程的进程id;

		   PROCINFO["ppid"]    the parent process ID of the current process.
		   当前进程的父进程的进程id;

		   PROCINFO["uid"]     the value of the getuid(2) system call.
		   系统调用getuid()的值。

		   PROCINFO["sorted_in"]
				       If this element exists in PROCINFO, then its value controls the order in which array  elements
				       are  traversed  in for loops.  Supported values are "@ind_str_asc", "@ind_num_asc",
				       "@val_type_asc",	  "@val_str_asc",   "@val_num_asc",   "@ind_str_desc",	 "@ind_num_desc",
				       "@val_type_desc",  "@val_str_desc",  "@val_num_desc", and "@unsorted".  The value can also
				       be the name of any comparison function defined as follows:
          如果此元素存在于PROCINFO数组中,它的值控制来循环时,数组中元素的横穿的顺序(简单来说就是这个值会影响数组元素的
		  排序或顺序)。支持的值有"@ind_str_asc", "@ind_num_asc", "@val_type_asc",	  "@val_str_asc",   "@val_num_asc",   
		  "@ind_str_desc",	 "@ind_num_desc", "@val_type_desc",  "@val_str_desc",  "@val_num_desc", and "@unsorted". 
		  它的值可以是下面这种定义形式的比较函数的名字。
					   
			  function cmp_func(i1, v1, i2, v2)

		   where i1 and i2 are the indices, and v1 and v2 are the corresponding values of the  two  elements  being  compared.
		   It should return a number less than, equal to, or greater than 0, depending on how the elements of the
		   array are to be ordered.
		   i1和i2是指数,v1和v2是要比较的两个元素的值。返回值应该小于,等于或大于0,具体的返回值取决于数组中的元素的顺序。

		   PROCINFO["version"]
			  the version of gawk.
			  当前gawk的版本信息。
			  
			  
			  

       RS	   The input record separator, by default a newline.
	          输入记录分隔符,默认是换行符。
			  

       RT	   The record terminator.  Gawk sets RT to the input text that matched the character or regular expression specified by RS.
		   内建变量RT记录着记录终止符。设置后,每次读取一条记录,读取一条记录后终止。

       RSTART	   The	index  of  the	first  character matched by match(); 0 if no match.  (This implies that character indices
		   start at one.)
		   被match()函数所匹配的第一个字符的索引,如果没有匹配,设置为0
		   (这意味着字符索引从头开始)

       RLENGTH	   The length of the string matched by match(); -1 if no match.
					被match()函数所匹配到的字符串的长度,没有匹配,值为-1。

       SUBSEP	   The character used to separate multiple subscripts in array elements, by default "\034".
					分隔数组元素多个下标的字符,默认为"\034"
					

       TEXTDOMAIN  The text domain of the AWK program; used to find the localized translations for the program's strings.
					AWK程序的文本域说明;用于查找程序字符串的本地环境翻译。

PS:本小节不给示例,其他章节引用内建变量的时候会说明。

四、awk的模式和处理动作以及正则表达式

4.1、概述

awk是面向行(默认处理记录是行)的语言。首先是模式,然后是对应的处理动作。处理动作语句包含在{}中。模式可以单独省略,处理动作也可以单独省略(如果都省略,也行,就是没有了)。{ print }这种特殊的形式等价于{ print $0}。
注释以字符#开始,然后直到该行结束。空行可以用来分隔语句,一般来说,一个语句结束是按照换行符来的,但是,如果有下面的情况,行结尾有逗号,{,?,:,&&,||,前面的结束规则并不使用。如果一行以do或else结束,语句也会继续。还有如果行以符号(通常叫续行符)结束,行也会继续到下一行。多个语句放在同一行,可以使用分号";"来分隔开。一般来说如果多个语句使用分号来隔开,通常是放在同一组模式-动作中的动作部分,那么这些语句只适用于对应的这组模式。

4.2、awk的模式种类介绍

4.2.1、BEGIN模式

BEGIN模式的大概表现形式为:

BEGIN{actions}...

BEGIN模式出现的位置不一定是最前面一组,而且可以出现多个BEGIN模式。示例:

[root@node1 ~]# awk 'BEGIN{print "hello,world"}'
hello,world
[root@node1 ~]# awk 'BEGIN{print "hello,world"}BEGIN{print "xixihaha"}'
hello,world
xixihaha
#书写位置放在普通模式后面,执行顺序不会改变的,BEGIN模式会在普通模式前面执行,之前流程处理小结已经简单
#阐述过了,这里不再赘述。
[root@node1 ~]# echo 123 | awk '{print "this is a test line"}BEGIN{print "name: xiaotang"}'  
name: xiaotang
this is a test line
4.2.2、END模式

END模式的大概表现形式为:

END{actions}...

END模式出现的位置不一定是最后面一组,而且可以出现多个END模式。示例:

[root@node1 ~]# echo 123 | awk 'END{print "this is a last line"}{print "name: wuyutong"}'
name: wuyutong
this is a last line
[root@node1 ~]# echo 123 | awk 'END{print "this is a last line"}{print "name: wuyutong"}END{print "-----"}'
name: wuyutong
this is a last line
-----
4.2.3、BEGINFILE模式

BEGINFILE和BEGIN的区别在于处理多个文件,如果处理单个文件,输出结果可能一样(当然内部的实现原理肯定不只是这么简单)。BEGINFILE对应处理动作部分会在命令行指定的每个输入文件读取其第一个记录之前执行,有
多少个输入文件,BEGINFILE模式的action会执行多次。其表现形式和BEGIN几乎一样:

BEGINFILE{actions}...

示例:

[root@node1 ~]# echo 123 > yanhui1
[root@node1 ~]# echo 234 > yanhui2
[root@node1 ~]# awk 'BEGIN{print "title:--------"}{print "name: xiaotang"}' yanhui1 
title:--------
name: xiaotang
[root@node1 ~]# awk 'BEGINFILE{print "title:--------"}{print "name: xiaotang"}' yanhui1  #单独处理一个文件,效果和BEGIN结果一样
title:--------
name: xiaotang
#BEGIN处理两个文件,其action部分只会执一次。
[root@node1 ~]# awk 'BEGIN{print "title:--------"}{print "name: xiaotang"}' yanhui1 yanhui2
title:--------
name: xiaotang
name: xiaotang
#BEGINFILE处理两个文件,其action部分执行了两次。
[root@node1 ~]# awk 'BEGINFILE{print "title:--------"}{print "name: xiaotang"}' yanhui1 yanhui2
title:--------
name: xiaotang
title:--------
name: xiaotang
#也可以使用多个BEGINFILE模式
[root@node1 ~]# awk 'BEGINFILE{print "title:--------"}{print "name: xiaotang"}BEGINFILE{print "second title:ooooooooooo"}' yanhui1 yanhui2
title:--------
second title:ooooooooooo
name: xiaotang
title:--------
second title:ooooooooooo
name: xiaotang
4.2.4、ENDFILE模式

理解ENDFILE模式,可以对比BEGINFILE模式和BEGIN模式的共同点和区别。
ENDFILE对应处理动作部分会在命令行指定的每个输入文件读取其最后一个记录后执行,同样是有多少个输入文件,该模式的action部分会执行多次。表现形式如下:

ENDFILE{action}...

示例:

[root@node1 ~]# cat yanhui1 
123
[root@node1 ~]# cat yanhui2
234
[root@node1 ~]# awk 'END{print "Ending Tag."}{print "name: wuxiaotong"}' yanhui1 
name: wuxiaotong
Ending Tag.
[root@node1 ~]# awk 'END{print "Ending Tag."}{print "name: wuxiaotong"}' yanhui1 yanhui2 
name: wuxiaotong
name: wuxiaotong
Ending Tag.
[root@node1 ~]# awk 'ENDFILE{print "Ending Tag."}{print "name: wuxiaotong"}' yanhui1 yanhui2 
name: wuxiaotong
Ending Tag.
name: wuxiaotong
Ending Tag.
4.2.5、正则表达式模式(/regular expression/)

对于正则表达式模式(/regular expression/),只有当输入记录匹配到正则表达式时,相关的语句才会被执行。正则表达式语法和egrep中的类似。在awk的4.0版本以后,几乎能支持全部egrep的语法(具体awk支持的正则语法,请参考下面章节或小节讲解的正则表达式的语法部分),表现形式为:

/regx/{action}...

示例:

#查看用户登录shell为/sbin/nologin的用户的信息
[root@node1 ~]# awk -F':' '$NF~/\/sbin\/nologin$/{print}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
avahi-autoipd:x:170:170:Avahi IPv4LL Stack:/var/lib/avahi-autoipd:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin
mysql:x:27:27:MariaDB Server:/var/lib/mysql:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
nginx:x:997:995:nginx user:/var/cache/nginx:/sbin/nologin
redis:x:996:994:Redis Database Server:/var/lib/redis:/sbin/nologin
geoclue:x:995:993:User for geoclue:/var/lib/geoclue:/sbin/nologin

#查看挂载配置文件中,以UUID开头的行
[root@node1 ~]# awk '/^UUID/' /etc/fstab
UUID=a73ff310-545f-46ba-910d-2acba6515f2f /boot                   ext4    defaults        1 2
4.2.6、关系表达式模式(relational expression)

支持的操作符有:
(1) 分组:(…)

(2) 字段引用:$
比如:$1,$0

(3) 自增运算:++(自增),–(自减)
比如:x++或++x,y–或--y

(4) 一元操作符:+,-,!

(5) 算术运算符:加(+),减(-),乘(*)
除(),取余(%),求幂(^或**)

例如:
x+y
x-y
x*y
x/y
x%y
x^y或x**y

(6) 比较操作符
大于(>),大于等于(>=),小于(<),小于等于(<=),不等于(!=),等于或等值比较(==)

(7) 正则匹配符
~:是否匹配后边的正则表达式;
!~:是否不匹配后边的正则表达式;
注意:不要在~ 或 !~的左边使用一个常量正则表达式。常量正则表达式要放在操作符右边。表达式/foo/ ~ exp与 (($0 ~ /foo/) ~ exp)意义相同,这往往不是我们需要的。
(8) 逻辑操作符
&&:逻辑与
||:逻辑或
!:逻辑非
(9) 赋值操作
=:赋值;
+=:加等,先相加后赋值;
-=:减等,先相减后赋值;
*=:乘等,先相乘后赋值;
/=:除等,先相除后赋值;
%=:取余等,先取余后赋值;
^=:幂等,先求幂后赋值;

4.2.7、多个模式参与逻辑运算

pattern1 && pattern2 …:模式1和模式2结果都为真,才为真;只要模式1为假,模式2的部分不会计算;(逻辑与)
pattern1 || pattern2 …:模式1或模式2结果有一个为真,就为真。只要模式1为真,模式2的部分不会计算;(逻辑或)
!pattern1:模式1结果为真,整个模式结果为假,反正,整个模式结果为真。(逻辑非)

4.2.8、条件表达式
expr1 ? expr2 : expr3

如果expr1为真,整个表达式的值取expr2表达式的值;如果expr1为假,整个表达式的值取expr3表达式的值。表达式expr2和表达式expr3只能取一个。

4.2.9、单独一个模式

pattern1

4.2.10、地址定界(pattern1, pattern2)

pattern1,pattern2
/pattern1/,/pattern2/ 是地址定界,范围模式。对于所有输入记录中,能够被pattern1匹配开始,直到被pattern2匹配结束,并且包含边界。这种形式不支持与其他模式表达式一起结合使用。

4.2.11、I/O管道操作符

|和|&
具体用法等将I/O语句的时候指明;

4.2.12、数组成员判断符

in
例如(i in a)

4.3、awk支持的正则表达式语法简要说明

正则表达式是和egrep的差不多的,支持扩展的正则表达式语法。常用的由以下字符组成:
.:配包含换行符在内的任意字符;
^:匹配字符串开始的字符;
$:匹配字符串的结束;
[abc…]:字符串列表,匹配abc…中任意字符;
[^abc…]:字符列表取反,匹配除了abc…之外的任意字符;
r1|r2:要么匹配r1,要么匹配r2;
r1r2:匹配r1后边跟着r2,这种属于级联;
r+:匹配r 一次或多次;
r*:匹配r 0次或多次;
r?:匹配r 0次或1次;
®:分组用法;
r{n}:匹配r重复n次;
r{n,}:匹配r至少重复n次;
r{n,m}:匹配r至少重复n此,至多重复m此;
\y: 匹配单词开头或结尾的空串;
\B:匹配单词内的空串;
<: 匹配单词开始的空串。(词首锚定)
>: 匹配单词结尾的空串。(词尾锚定)
\s:匹配任意空白字符;
\S:匹配任意非空白字符;
\w:匹配任意单词组成字符(信件字符,数字,或下划线)
\W:匹配任意非单词字符。
字符串列表中支持配合使用POSIX标准的正则语法:
[:alnum:]:匹配字母数字字符;
[:alpha:]:字母字符(大小写字母)
[:blank:]:匹配字母字符(大小写字母)
[:cntrl:]:匹配控制字符;
[:digit:]:匹配数字字符;
[:graph:]:匹配可打印和可显式字符。(空格可以打印不过不可见,字符a既可以打印也可见);
[:lower:]:匹配小写字母字符;
[:print:]:匹配可打印字符(可打印字符不属于控制字符);
[:punct:]:匹配标点符号字符(除了书信字符,数字字符,控制字符,以及空格字符之外);
[:space:]:匹配空白字符(空格,横向制表符,表单符);
[:upper:]:匹配大写字母字符;
[“xdigit”]:匹配16进制数字字符。

4.4、awk的处理动作语句种类介绍

处理动作语句使用花括号括起来的部分,常见的actions 包括:(一般)赋值语句,条件判断,循环语句,控制语句,输入/输出语句。以下介绍几个常用的:

4.4.1、控制语句(control statements)和循环语句(looping statements)

(1) 单分支if语句

if (condition) statement
如果条件为真,执行if中的语句;如果条件为假,不执行if中的语句。
使用场景:对awk取得的整行或某个字段做条件判断;

示例:

[root@node1 ~]# awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd #打印用户的登录shell是bash的用户名
root
yanhui
centos
gentoo

[root@node1 ~]# awk '{if(NF>5) print $0}' /etc/fstab  #打印/etc/fstab中以空格分隔的字段,字段数量大于5个的记录
# Created by anaconda on Tue Oct 16 11:04:47 2018
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
/dev/mapper/centos-root /                       xfs     defaults        0 0
UUID=a73ff310-545f-46ba-910d-2acba6515f2f /boot                   ext4    defaults        1 2
/dev/mapper/centos-swap swap                    swap    defaults        0 0

(2) 双分支if语句

if (condition) statement else statement
如果if条件为假,执行else语句;如果if条件为真,执行if中的语句。
使用场景:对awk取得的整行或某个字段做条件判断;

示例:

#在CentOS 7上操作,通过用户id区分系统用户和普通用户,用户id大于等于1000属于普通用户。
[root@node1 ~]# awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else {printf "root or Sysuser: %s\n",$1}}' /etc/passwd
root or Sysuser: root
root or Sysuser: bin
root or Sysuser: daemon
root or Sysuser: adm
root or Sysuser: lp
root or Sysuser: sync
root or Sysuser: shutdown
root or Sysuser: halt
root or Sysuser: mail
root or Sysuser: operator
root or Sysuser: games
root or Sysuser: ftp
root or Sysuser: nobody
root or Sysuser: avahi-autoipd
root or Sysuser: dbus
root or Sysuser: polkitd
root or Sysuser: tss
root or Sysuser: postfix
root or Sysuser: sshd
root or Sysuser: systemd-network
root or Sysuser: tcpdump
root or Sysuser: ntp
root or Sysuser: mysql
Common user: yanhui
root or Sysuser: chrony
root or Sysuser: nginx
Common user: centos
Common user: gentoo
root or Sysuser: redis
root or Sysuser: geoclue

(3) while循环语句

while (condition) statement
条件为真,进入循环;条件为假,退出循环;
使用场景:对一行内的多个字段逐一类似处理时使用;对数组中的各元素逐一处理时使用;

示例:

#打印以0个或多个空格开头(包括制表符)后边跟"linux16"字符串的行的所有以空格分隔后的每个字段的值以及字段的字符长度。
[root@node1 ~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
linux16 7
/vmlinuz-3.10.0-229.el7.x86_64 30
root=/dev/mapper/centos-root 28
ro 2
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
rhgb 4
quiet 5
linux16 7
/vmlinuz-0-rescue-323ab78cbeea47f798acfa7d04f4ea32 50
root=/dev/mapper/centos-root 28
ro 2
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
rhgb 4
quiet 5

[root@node1 ~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=7) {print $i,length($i)}; i++}}' /etc/grub2.cfg
linux16 7
/vmlinuz-3.10.0-229.el7.x86_64 30
root=/dev/mapper/centos-root 28
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
linux16 7
/vmlinuz-0-rescue-323ab78cbeea47f798acfa7d04f4ea32 50
root=/dev/mapper/centos-root 28
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21

(4) do while循环语句

do statement while (condition)
意义:至少自行一次循环体
示例:
[root@node1 ~]# awk -v i=1 'BEGIN{do {i--;print "title-----"} while(i>0)}'
title-----
[root@node1 ~]# awk -v i=2 'BEGIN{do {i--;print "title-----"} while(i>0)}'
title-----
title-----

(5) for循环语句

for (expr1; expr2; expr3) statement
for(variable assignment;condition;iteration process) {for-body}
expr1为variable assignment,变量赋值;(通常是控制循环的初始变量赋值)
expr2为condition,条件判断;
expr3表示迭代过程;

示例:

用for循环实现之前的一个案例。
以有空格或者无空格开头(包括制表符)后边跟"linux16"字符串的行,以空白作为分隔符,打印每个字段值以及字符长度。
[root@node1 ~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg 
linux16 7
/vmlinuz-3.10.0-229.el7.x86_64 30
root=/dev/mapper/centos-root 28
ro 2
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
rhgb 4
quiet 5
linux16 7
/vmlinuz-0-rescue-323ab78cbeea47f798acfa7d04f4ea32 50
root=/dev/mapper/centos-root 28
ro 2
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
rhgb 4
quiet 5

(6) 特殊for循环语句

for (var in array) statement
能够遍历数组中的元素;
语法:for(var in array) {for-body}

示例:

#统计TCP的状态种类对应的次数
[root@node1 ~]# netstat -natu | awk '/^tcp\>/{a[$NF]++}END{for(i in a){print i,a[i]}}'
LISTEN 2
ESTABLISHED 2

(7) continue语句
示例:

[root@node1 ~]# awk -v i=6 'BEGIN{while(i>0){if(i==4){i--;continue};print i;i--}}'
6
5
3
2
1

(8) 删除数组和元素语句

delete array[index] #删除数组指定元素
delete array #删除整个数组

示例:

[root@node1 ~]# echo 1 | awk 'BEGIN{a[0]=1;a[1]=2;a[2]=3;}BEGIN{delete a[2]}END{for(i in a){print i,a[i]}}'
0 1
1 2
[root@node1 ~]# echo 1 | awk 'BEGIN{a[0]=1;a[1]=2;a[2]=3;}BEGIN{delete a}END{for(i in a){print i,a[i]}}'
[root@node1 ~]# 

(9) exit退出语法

exit [ expression ]
[root@node1 ~]# awk -v a=6 'BEGIN{while(a>0){if(a==3) {exit} print a;a--}}'
6
5
4
[root@node1 ~]# echo $?
0
[root@node1 ~]# awk -v a=6 'BEGIN{while(a>0){if(a==3) {exit 112} print a;a--}}'
6
5
4
[root@node1 ~]# echo $?
112

(10) switch分支语句

switch (expression) {
case value|regex : statement
...
[ default: statement ]
}
语法:switch(expression) {case VALUE1 or /REGEXP/: statement; case VALUE2 or /REGEXP2/: statement; ...; default: statement}
说明:case分支语句每个都会执行,最好是在非default分支的语句后面加上break语句。

示例:(这里存储为了举例子,简单模拟,这样做无实际作用)

[root@node1 ~]# awk -v a=1 'BEGIN{switch(a){case 1: {print "1";break}; case 2: {print "2";break}; default: print a}}'
1
[root@node1 ~]# awk -v a=2 'BEGIN{switch(a){case 1: {print "1";break}; case 2: {print "2";break}; default: print a}}'
2
[root@node1 ~]# awk -v a=3 'BEGIN{switch(a){case 1: {print "1";break}; case 2: {print "2";break}; default: print a}}'
3
[root@node1 ~]# awk -v a=123 'BEGIN{switch(a){case 1: {print "1";break}; case 2: {print "2";break}; default: print a}}'
123
[root@node1 ~]# awk -v a="hello,world" 'BEGIN{switch(a){case 1: {print "1";break}; case 2: {print "2";break}; default: print a}}'
hello,world
4.4.2、输入/输出语句(IO statements)

(1) close(file [, how])
关闭文件、管道或者co-process(这个东西不知道是什么,所以我就给出字面含义)。

(2) getline
设置$0来自于下一个输入记录。并且会改变设置NF,NR,FNR内建变量的值。

[root@node1 ~]# cat file.txt 
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDmEfSd1dpVcdQ5WDKJmK4baeJRLYM+jafojezo/XVvN6URr6tU
MTCg+czurtrRyKcrPAsau76X6HJ9pU8j9ZHJ5TmU1MRZhRLBzuWyaoRznbdtUw+i
LCLn7feRh1IQJ8fFTWo+zdzRa0HyU8H2sotJBj8lKvBoId3gcCASNtG8NQIDAQAB
AoGAKGG4bMevZdXv6QzI+gu5a4hfN95kLeOROClJDvAF37qFl6Ac0086+NY/mRlJ
wQ6WAOmuDUgswN8E0XbleCtPJipvHuEMNODgCqEdtMm25zsQLR8cgTgfvRlLdj/D
YgBiu/TDBkP5n5x4V6HtnpMbrn3ldpxUvK+5YR8gdhQm86ECQQD/Q4o6jfKDIUO4
tWEhgd0VNmFxO0gzcfqy9HB8T3JLAS5DJb75EXXC7boYxkrhlIqtEIVfxiM/7Q5H
TC6wdJ/NAkEA5rvQsON1mud3bcypVtCrz0+GWGms8lO9YaJw46B/0r6ddfXTu5qm
ofzBRWSSAab30gGAfD2s02JqCf3QD26WCQJBAKWeE5xkJrmPppm8DPYphODTdKt7
1B+Uzxy23hi5jyU88eAdKGf/PRGpHjoexczQjJ02/+Ig8xPx4Xa+5fgU4u0CQHLG
8QxdSg5xoEZdbcuC6ESrQaBPiTnOkEG2RcPl/k/+hGO+ksIbL1MbYaU8Xyk1PsgX
+StUntlUdn1b5hkXIMkCQQCOmoLsz+5vWd6p3D4cIR7o5BFUPVZxlo6IMLIVPKp4
Oi1rkJ3v+P9e1xyQNFnJdYbNtFUJcp63yfHRb0bAQlbX
-----END RSA PRIVATE KEY-----
#这里使用getline命令执行后,默认没有加上重定向符号或管道,也没有指定变量,默认形式是读取默认RS设置的记录分隔符分隔的下一行记录,BEGIN的下一行就是第一行,读入下一行后,NF的值也会随着不同记录中分隔的字段数目不一样,与此同时NR和FNR等内建变量的值也在随着改变。
[root@node1 ~]# awk 'BEGIN{getline;print NR}' file.txt  
1
[root@node1 ~]# awk 'BEGIN{getline;print $0}' file.txt 
-----BEGIN RSA PRIVATE KEY-----
[root@node1 ~]# awk 'BEGIN{getline;print NF}' file.txt 
4

(3) getline <file
设置$0来自于指定文件的下一个记录,设置了NF

(4) getline var
设置了var来自于下一个输入记录,设置了NR,FNR

(5) getline var <file
设置var来自于指定文件的下一个记录。

(6) command | getline [var]
把运行命令的结果通过管道的形式给到$0或var变量

(7) command |& getline [var]
cmd1 |& cmd2 这种形式,在bash 4.0以后版本支持的语法,表示把cmd1的标准输出和标准错误都通过管道给cmd2.这里的command |& getline [var]也可以这里理解。

(8) next
停止处理当前的输入记录。开始读入下一个输入记录,然后重新从第一个模式开始处理。如果已经读到了输入数据的结尾,那么会执行END块的部分(如果有)或者正常退出(没有END块)。
示例:

#如果处理的记录$0为3,我们就开始读入下一个输入记录值。
[root@node1 ~]# seq 6|awk '{if($0=="3")next;print $0}'
1
2
4
5
6

(9) nextfile
停止处理当前的输入文件。读取下一个输入文件,FILENAME和ARGIND内建变量的值被重新更新,FNR被重新设置为1,并且开始从第一个模式开始处理。如果已经读到了输入数据的结尾,那么会执行END块的部分(如果有)或者正常退出(没有END块)。

(10) print
打印当前的记录。输出记录以ORS内建变量的值为结束,默认ORS为换行符。
这种形式是我们最常见的,默认行为是ORS输出记录分隔符是换行,所以print默认行为是每打印一次,会换行一次。

(11) print expr-list
打印表达式。每个表达式被OFS内建变量分隔的,默认的OFS的值为空白(空格)。输出记录以ORS内建变量的值为结束,默认ORS为换行符。

(12) print expr-list >file
打印表达式的覆盖到一个文件。每个表达式被OFS内建变量分隔的,默认的OFS的值为空白(空格)。输出记录以ORS内建变量的值为结束,默认ORS为换行符。

(13) printf fmt, expr-list
以指定格式打印表达式列表。可以参考下文提到的printf语句。

(14) printf fmt, expr-list >file
以指定格式输出覆盖写入指定文件。

(15) system(cmd-line)
在awk中执行命令行的命令,返回命令的退出状态码。(这一特性在非POSIX系统中可能不可用)
示例:

[root@node1 ~]# awk 'BEGIN{mycmd="echo Myname is xiaotang.";system(mycmd)}'
Myname is xiaotang.

(16) fflush([file])
刷新awk打开的输出文件以及管道文件相关联的缓冲。如果文件没有给定或者是一个空串,flush将会刷新所有打开的文件和管道。

(17) print … >> file
追加输出到指定文件中。

(18) print … | command
把打印结果写入管道,通过管道传递给后边的命令。

(19) print … |& command
发送特殊到一个co-process 或一个套接字。
PS:我觉得就是把print的标准输出和标准错误通过管道传递给后边的内容。

4.4.3、printf 语句

基本语法格式:

printf format, item1, item2, …
format是格式化输出定义部分,
item是表示被printf输出的项。

AWK版本中的printf语句和sprintf()函数接受以下的(格式)转换规范:

printf格式化输出参数:
%c:只打印单个字符。如果待打印的参数是一个数值,配合%c格式,会输出数值的字符表示形式(对照ASCII码表);如果待打印的参数是一个字符串,包含多个字符,那么指定%c格式后,只会打印字符串的第一个字符。

%d, %i:指定%d或%i后会打印十进制数值(只会打印整数部分,不会做是四舍五入);

%e, %E:科学计数法数值显示;显示格式为[-]d.dddddde[+-]dd或[-]d.ddddddE[+-]dd。

%f,%F:以浮点数格式打印数据;显示格式为[-]ddd.dddddd。

%g,%G:%g或%G会使用%e或%f的格式来显示(科学计数法或浮点数),会去掉没用的0,比如前导0.在科学计数法中,%G会使用E而%g会使用e。

%o:无符号八进制整数;

%u:无符号十进制整数;

%s:字符串的形式输出;

%x, %X: 无符号16进制整数。对于十六进制中的10以上到15的字母表示形式,如果使用的是%X,则为A-F,
如果使用的是%x,则为a-f。

 %%: 打印%字符本身;

示例:

(1) %c的运用
[root@localhost ~]# echo 9 | awk '{printf "%c\n",$0}'
	
 #十进制数字9对应的ASCII码表示横向制表符	
[root@localhost ~]# echo 9 | awk '{printf "%c\n",$0}'|cat -A  
^I$
[root@localhost ~]# echo 13 | awk '{printf "%c\n",$0}'

[root@localhost ~]# echo 13 | awk '{printf "%c\n",$0}'|cat -A
^M$
[root@localhost ~]# echo 64 | awk '{printf "%c\n",$0}'|cat -A
@$
[root@localhost ~]# echo 64 | awk '{printf "%c\n",$0}'
@
[root@localhost ~]# echo 65 | awk '{printf "%c\n",$0}'
A
#这里一个字符串,只输出来单个字符,单个字符是字符串的首个字母
[root@localhost ~]# echo hello | awk '{printf "%c\n",$0}'
h

(2) %d或%i的运用(%d和%i效果一样)
[root@localhost ~]# echo hello | awk '{printf "%i\n",$0}'
0
[root@localhost ~]# echo hello | awk '{printf "%d\n",$0}'
0
[root@localhost ~]# echo 12 | awk '{printf "%d\n",$0}'
12
[root@localhost ~]# echo 12 | awk '{printf "%i\n",$0}'
12
[root@localhost ~]# echo 12.23433 | awk '{printf "%i\n",$0}'
12
[root@localhost ~]# echo 12.23433 | awk '{printf "%d\n",$0}'
12
[root@localhost ~]# echo 12.63433 | awk '{printf "%d\n",$0}'
12
#不会对结果做"五入"的,只会截取整数部分
[root@localhost ~]# echo 12.63433 | awk '{printf "%i\n",$0}'
12

(3) %e或%E的运用
[root@node1 ~]# echo 12222222222|awk '{printf"%e\n",$0}'
1.222222e+10
[root@node1 ~]# echo 12222222222|awk '{printf"%E\n",$0}'
1.222222E+10
[root@node1 ~]# echo -12222222222|awk '{printf"%E\n",$0}'
-1.222222E+10
#可以看到我这里换上来%E来替代%e,输出结果中的e字母变成来大写的E
[root@node1 ~]# echo -122228029380598209322222|awk '{printf"%E\n",$0}'
-1.222280E+23
[root@node1 ~]# echo -0.11174447138|awk '{printf"%E\n",$0}'
-1.117445E-01

(4) %f或%F的运用
[root@localhost ~]# echo "1"|awk '{printf "%f\n",$0}'
1.000000
[root@localhost ~]# echo "222111"|awk '{printf "%f\n",$0}'
222111.000000
[root@localhost ~]# echo "1909090190190910190111"|awk '{printf "%F\n",$0}'
1909090190190910111744.000000
[root@localhost ~]# echo "1909090190190910190111111111111111111111111111"|awk '{printf "%F\n",$0}'
1909090190190910183807482106976871498047815680.000000

(5) %g或%G的运用
[root@node1 ~]# echo 1.2222223333| awk '{printf "%g\n",$0}'
1.22222
[root@node1 ~]# echo 12222223333| awk '{printf "%g\n",$0}'
1.22222e+10
[root@node1 ~]# echo 12222223333| awk '{printf "%G\n",$0}'
1.22222E+10
[root@node1 ~]# echo 0.12222223333| awk '{printf "%G\n",$0}'
0.122222

在参数和%之间可以加一些控制字符,支持控制字符/修饰字符如下。

printf格式化输出附加修饰字符:

(1) N$
一个整数常量后边跟着一个美元符"$",表示位置说明符。一般来说,格式规范是按照参数在格式化字符串
中给定的顺序来引用生效的。有了位置修饰符,格式规范按照特定参数应用或生效,而不是接下来列表中
的参数。位置修饰符从1开始计数。(如果同时格式化输出多个item,每个item都应该指明位置说明符)
示例:
[root@localhost ~]# awk 'BEGIN{printf "%s %s %s\n","dont","panic","xiaotang"}'
dont panic xiaotang
正常格式化输出(这里是字符串形式)一串文本,这里打印来3个字符串项。正常顺序即为默认从左到右的输出,
第一个item是"dont",第二个item是"panic","第3个item"是"panic"。此时,如果我使用N$的形式,添加至格式
化的字符串格式中,添加到"%"和参数控制选项之间,例如%3$s,那么格式化的顺序,如果这个放在第一个,
那么格式化输出结果,第三个item就显示在第一个。例如:
[root@localhost ~]# awk 'BEGIN{printf "%3$s %1$s %2$s\n","dont","panic","xiaotang"}'
xiaotang dont panic
如果我有用到这个位置说明符,又有其他格式化控制没有用到,会报错:
[root@localhost ~]# awk 'BEGIN{printf "%3$s %1$s %$s\n","dont","panic","xiaotang"}'
awk: cmd. line:1: fatal: arg count with `$' must be > 0

(2) #
 使用指定控制字符的替代形式。简单来说如果我原先这样用,%o换成来%#o,%x或%X换成了%#x或%#X等,
 还有%e,%E,%f,%F,%g,%G都可以在%字符和后边的控制格式字母之间加上一个字符"#"。
 %#o:输出结果,八进制会带上数字0作为前缀表示;%o原先输出八进制数字不会带上0前缀;
 %#x或%#X:输出结果,十六进制会带上0x或0X作为前缀;%x或%X原先输出不会带上十六进制表示的前缀0x或0X;
 而 %e, %E, %f and %F配合字符"#"后,结果总是会包含小数点字符;
 %g和%G配合字符"#"后,结果的前导0不会被移除,原先会移除无用的前导0.
例如:
十进制数字15转换成八进制表示位17,如果配合#符号,会输出前导的0作为显式指明结果为八进制数值。
[root@localhost ~]# echo 15 | awk '{printf "%#o\n",$0}'
017
[root@localhost ~]# echo 15 | awk '{printf "%o\n",$0}'
17
[root@localhost ~]# echo 23 | awk '{printf "%#x\n",$0}'
0x17
[root@localhost ~]# echo 27 | awk '{printf "%#x\n",$0}'
0x1b
[root@localhost ~]# echo 27 | awk '{printf "%#X\n",$0}'
0X1B
[root@localhost ~]# echo 27 | awk '{printf "%X\n",$0}'
1B
(3) - 
左对齐。
例如:
[root@node1 ~]# awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or SysUser";printf "%-15s:%s\n",$1,usertype}' /etc/passwd
root           :Sysadmin or SysUser
bin            :Sysadmin or SysUser
daemon         :Sysadmin or SysUser
adm            :Sysadmin or SysUser
lp             :Sysadmin or SysUser
sync           :Sysadmin or SysUser
shutdown       :Sysadmin or SysUser
halt           :Sysadmin or SysUser
mail           :Sysadmin or SysUser
operator       :Sysadmin or SysUser
games          :Sysadmin or SysUser
ftp            :Sysadmin or SysUser
nobody         :Sysadmin or SysUser
avahi-autoipd  :Sysadmin or SysUser
dbus           :Sysadmin or SysUser
polkitd        :Sysadmin or SysUser
tss            :Sysadmin or SysUser
postfix        :Sysadmin or SysUser
sshd           :Sysadmin or SysUser
systemd-network:Sysadmin or SysUser
tcpdump        :Sysadmin or SysUser
ntp            :Sysadmin or SysUser
mysql          :Sysadmin or SysUser
yanhui         :Common User
chrony         :Sysadmin or SysUser
nginx          :Sysadmin or SysUser
centos         :Common User
gentoo         :Common User
redis          :Sysadmin or SysUser
geoclue        :Sysadmin or SysUser

(4) +
显示数值的符号。

五、awk的数组

数组通过方括号([和])包含下标表达式。数组下标表示部分可以是一个表达式的列表(expr,expr …),下标表示部分的表达式可以通过SUBSEP内建变量指定的分隔符分隔多个字符串的值。这种可以使用表达式多个表达式下标的能力,让(awk)的数组变得更加丰富多样化。请看下面的示例:

i = "A"; j = "B"; k = "C"
x[i, j, k] = "hello, world\n"

说明:数组x,下标索引为"A,B,C" ,其值为"hello, world\n"。awk中的数组都是关联数组,索引可以是字符串值。

数组表示形式语法:

array[index-expression]
#array表示数组名
#index-expression表示索引表达式
其中index-expression使用说明:
(1) 可使用任意字符串;字符串要使用双引号;
(2) 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空串”;

可以通过下面的这种"val in array"操作来判断是否某个索引位置对应的数组中的元素是某个特殊的值。
数组有多个下标索引,(上面的判断元素是否存在的操作)使用"(i,j) in array"这种形式。

if (val in array)
  print array[val]
in结构语句也可以用于循环来迭代遍历数组中的所有元素:
for(val in array) {statements}
for(val in array) {print val,a[val]}

删除数组和数组元素:
可以使用delete删除数组的指定元素(要指定要删除元素对应的索引)。也可以使用delete语句删除整个数组的
所有元素,即为删除整个数组,这个时候不能指定下标,直接指定数组名即可。

多维数组(以二维数组为例):
多维数组是模拟的(在一纬数组基础上)

a[1] = 5
a[2][1] = 6
a[2][2] = 7

一维数组使用示例:

#手动给数组赋值,比较笨重,仅仅用于演示,并且使用for(val in array)特殊结构的for循环来遍历数组的每一个元素
[root@localhost ~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
Tuesday
Monday

#统计TCP状态种类和次数,生产脚本有时候用到,不过建议用ss替代netstat,而且相应字段应该略有调整。
[root@localhost ~]# netstat -tan | awk '/^tcp\>/{state[$NF]++}END{for(i in state) { print i,state[i]}}'
LISTEN 2
ESTABLISHED 2
#如果要过滤掉ipv6或者只过滤出ipv4,要做相应调整,下面给出简单示例
[root@localhost ~]# ss -natu | awk '/^tcp\>/{state[$2]++}END{for(i in state) { print i,state[i]}}'
LISTEN 4
ESTAB 2
#统计挂载配置文件中,不同文件系统类型出现的次数,逻辑还是和上面一样
[root@localhost ~]# awk '/^UUID/{fs[$3]++}END{for(i in fs) {print i,fs[i]}}' /etc/fstab
swap 1
xfs 2

六、awk的函数

PS:我这里演示的是gnu awk的4.0.2版本,相较于CentOS 6.x 3.x的版本,属于较新版本,很多
内置函数选项和含义略有调整,所以建议学习时对比使用,

6.1、内置函数

6.1.1、数值函数(不举例,直接介绍含义)

(1) atan2(y, x)
返回y/x的反正切的弧度值。

(2) cos(expr)
返回expr的余弦,结果是一个弧度。

(3) exp(expr)
指数函数。

(4) int(expr)
截断为一个整数。

(5) log(expr)
自然对数函数。底数为e

(6) rand()
返回程序第一次产生时候的介于0和1之间的随机数(不是每一次都是随机的)

(7) sin(expr)
返回expr的正弦,结果是一个弧度。

(8) sqrt(expr)
平方根函数。

(9) srand([expr])
使用expr作为随机数生成器的新的种子。如果expr没有提供,将会使用今天的时间。
返回值随机数生成器的前一个种

6.1.2、字符串函数(重点)

(1) asort(s [, d [, how] ])

返回值为原数组s的元素的数量。使用gawk的内部默认规则(就是根据普通的规则对值做比较)对数组内容进行排序,并且会用
从1开始的整数,按照往后增加的顺序(1,2,3,4,...)的整数序列值去替换排序的数组s的下标索引。如果可选的目标数组d有
指定,会先复制s一份到d,然后对d排序,并且保留源数组s的索引不变。(建议使用d参数,保留原来数组的东西)
可选的字符串"how"控制比较的规则方面和比较的模式。how参数的有效值可以使内建变量PROCINFO(它是一个数组)中的数组
元素PROCINFO["sorted_in"]的值。这个数组的详细值和含义说明。也可以是用户自定义的存储在 PROCINFO["sorted_in"]的自定
义比较函数。通常how参数会省略掉,也是新版awk的一个参数。

简单总结:
(1) 不指定参数d,就是函数只有一个参数s。这个s是一个数组。函数对这个数组元素排序,丢弃原来数组的索引,新排序后的
数组索引,从1开始往后递增的序列值;
(2) 指定参数d,先把数组s复制一遍到d。然后对d数组进行第(1)步的操作;
(3) 新版awk参数now一般很少用,除非对asort函数的默认排序规则不满意;
(4) 函数返回值为第一个参数数组s的元素个数。

(2) asorti(s [, d [, how] ])

返回值为源数组s的元素的数量。行为和函数asort()很像,不过asorti不像asort函数,它是对数组的下标进行排序,
而不是数组的元素。排序后,数组的索引会从1开始的序列数字(1,2,3,...),并且把原先的下标索引作为数组元素的值存储。
原来数组的值就丢失来;因此,如果你希望保留原来的数组,请提供第二个参数d(一个数组)。
d提供后,同理,先把s拷贝一份,然后后面的行为操作都是对d进行。参数how的意义也和asort函数一样。

简单总结:
(1) 不指定参数d,函数调用只给一个参数s。s是一个数组。函数会对这个下标索引进行排序,然后把这些排序后的下标索引
以值的形式替换原来数组值存储到数组中,然后数组的小标索引设置为从1开始往后递增的序列值;
(2) 指定参数d,先复制数组s到d。然后对d数组进行第(1)步的操作;
(3) 新版awk参数now一般很少用,除非对asorti函数的默认排序规则不满意;
(4) 函数返回值为第一个参数数组s的元素个数。

asort函数和asorti函数的示例:

[root@node1 ~]# cat txt
12 772
19 226
257 112
#遍历数组,发现排序没不像我们想的那样
[root@node1 ~]# awk '{a[$1]=$2}END{for(i in a){print i,a[i]}}' txt
19 226
257 112
12 772
#这里asort函数只有一个参数a,是自动生成的一个数组。通过asort函数排序后,数组索引从1开始,然后序列自增。元素值从已经
#被排序过了
[root@node1 ~]# awk '{a[$1]=$2}END{alen=asort(a);for(i=1;i<=alen;i++){print i,a[i]}}' txt
1 112
2 226
3 772
[root@node1 ~]# awk '{a[$1]=$2}END{alen=asort(a,b);for(i=1;i<=alen;i++){print "i="i,"a[i]="a[i],"b[i]="b[i]}}' txt
i=1 a[i]= b[i]=112
i=2 a[i]= b[i]=226
i=3 a[i]= b[i]=772
#有了第二个参数后,原来数组的内容和下标都没有变化,默认被排序的是b数组。
[root@node1 ~]# awk '{a[$1]=$2}END{alen=asort(a,b);for(i=1;i<=alen;i++){print "i="i,"a[i]="a[i],"b[i]="b[i]}}END{print a[12]}' txt
i=1 a[i]= b[i]=112
i=2 a[i]= b[i]=226
i=3 a[i]= b[i]=772
772
[root@node1 ~]# awk '{a[$1]=$2}END{alen=asort(a,b);for(i=1;i<=alen;i++){print "i="i,"a[i]="a[i],"b[i]="b[i]}}END{print a[19]}' txt
i=1 a[i]= b[i]=112
i=2 a[i]= b[i]=226
i=3 a[i]= b[i]=772
226

[root@node1 ~]# awk '{a[$1]=$2}END{alen=asorti(a);for(i=1;i<=alen;i++){print i,a[i]}}' txt
1 12
2 19
3 257
[root@node1 ~]# cat txt
12 772
19 226
257 112
# asorti函数只传了一个参数,排序后。数组元素的值被排序后的下标索引值所替换了。然后新下标索引从1开始的递增序列值。
#alen接受函数的返回值,是原先数组的元素个数
[root@node1 ~]# awk '{a[$1]=$2}END{alen=asorti(a);for(i=1;i<=alen;i++){print i,a[i]}}END{print a[12]}' txt
1 12
2 19
3 257
#给第二个参数后,是在复制的数组上操作,原数组a保持默认不变。
[root@node1 ~]# awk '{a[$1]=$2}END{alen=asorti(a,b);for(i=1;i<=alen;i++){print "i="i,"a[i]="a[i],"b[i]="b[i]}}END{print a[12]}' txt
i=1 a[i]= b[i]=12
i=2 a[i]= b[i]=19
i=3 a[i]= b[i]=257
772

(3) gsub(r, s [, t])

以r表示的模式来查找t所表示的字符中的匹配的内容,并将其所有出现均替换为s所表示的内容;
函数返回值为返回成功替换的数量。如果t参数省略了,会使用$0。

(4) sub(r, s [, t])

以r表示的模式来查找t所表示的字符中的匹配的内容,并将其第一次出现替换为s所表示的内容;
如果t省略,表示从$0中查找。

gsub函数和sub函数示例:

#这里定义了一个变量字符串x,然后sub会替换第一次找到的数字串,然后替换成"xiaotang"。sub的返回值为1表示替换了一次
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print sub(/[[:digit:]]+/,"xiaotang",x);print x}'
1
hello,my name is xiaotang,are you sure?123
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print sub(/[[:digit:]]/,"xiaotang",x);print x}'
1
hello,my name is xiaotang23456,are you sure?123

[root@node1 ~]# cat newtxt 
hello,my name is 123456,are you sure?123
hello,my name is 234,are you sure 098?
[root@node1 ~]# cat newtxt |awk '{print sub(/[[:digit:]]+/,"xiaotang")}'
1
1
#这里省略了第三个参数,默认是用的是记录$0的值,而且没读入一行,也只替换了一处
[root@node1 ~]# cat newtxt |awk '{print sub(/[[:digit:]]+/,"xiaotang");print $0}'
1
hello,my name is xiaotang,are you sure?123
1
hello,my name is xiaotang,are you sure 098?

#换成gsub函数后,可以看到都是替换,相较于sub的差别就是gsub会替换所有匹配到的,不管一行有多少个符合匹配的记录
#有点类似于sed中的s/regex/replacement/与s/regex/replacement/g 的区别
[root@node1 ~]# cat newtxt |awk '{print gsub(/[[:digit:]]+/,"xiaotang");print $0}'
2
hello,my name is xiaotang,are you sure?xiaotang
2
hello,my name is xiaotang,are you sure xiaotang?
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print gsub(/[[:digit:]]/,"xiaotang",x);print x}'
9
hello,my name is xiaotangxiaotangxiaotangxiaotangxiaotangxiaotang,are you sure?xiaotangxiaotangxiaotang
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print gsub(/[[:digit:]]+/,"xiaotang",x);print x}'
2
hello,my name is xiaotang,are you sure?xiaotang

(5) gensub(r, s, h [, t])

 在字符串t中搜索(如果省略表示在$0中搜索),匹配正则表达式r。如果h是一个以g或G开头的字符串,会把所有t中
 被r所匹配到的所有字符串替换成s表达的值。如果h是一个数字,表示替换t中被r所匹配到的第h处的字符串替换成
 s表示的值,h要大于等于0,如果小于0会产生警告信息并且强制把其值作为1来处理。
 
 在替换文本s中,可以使用序列\n,n可以为1到9之间的任意单个数组,可以引用前r正则表达式中的第n个分组。例如引用
 第一个分组(小括号括起来的部分),表示语法为\1,由于\符号本身有特殊含义,所以要转移它。
 \0表示匹配所有r所匹配的实体,作用相当于符号&。请看下面的示例
 示例:
 echo "hello,world\!hello,awk\!hello Linux\!"|awk 'BEGIN{r="[a-zA-Z]+(,)[a-zA-Z]+"}{print gensub(r,"\\1uj","g")}'
 
 gensub()函数不像sub()和gsub()函数,它返回的是修改后的字符串的值,而原来的目标字符串t并没有修改或改变。
 
 概述:
 对于t中匹配r的字串,如果h是以”g”或”G”开头的字符串,则将匹配的所有子串替换为s,如果h是数字n,则将第n处匹配进行替换;如果参数t省略,则t为$0

gensub函数示例:

#这次指定的替换标识为"g",那么会替换所有匹配的。可以看到,整个匹配的字符串就是函数的返回结果。原始字符串没有变化
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print gensub(/[[:digit:]]+/,"tanger","g",x);print x}'
hello,my name is tanger,are you sure?tanger
hello,my name is 123456,are you sure?123
#替换表示为“G”或者以"G"或"g"开头的标识都可以表示替换所有匹配的
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print gensub(/[[:digit:]]+/,"tanger","G",x);print x}'
hello,my name is tanger,are you sure?tanger
hello,my name is 123456,are you sure?123
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print gensub(/[[:digit:]]+/,"tanger","G1g",x);print x}'
hello,my name is tanger,are you sure?tanger
hello,my name is 123456,are you sure?123
#如果我把替换表示换成数字,表示替换指定第几处匹配的,返回值也是只替换的结果
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print gensub(/[[:digit:]]+/,"tanger","G1g",1);print x}'
tanger
hello,my name is 123456,are you sure?123
[root@node1 ~]# awk -v x="hello,my name is 123456,are you sure?123" 'BEGIN{print gensub(/[[:digit:]]+/,"tanger","G1g",2);print x}'
tanger
hello,my name is 123456,are you sure?123

#这里用到了正则中的分组的概念以及对分组进行后向引用。r表示的正则模式匹配的是大小写母中间跟一逗号,后面也是大小写字母。
#很显然,我们的$0就是前边echo的字符串的内容,中间能被匹配的内容有"hello,world","hello,awk"。
#由于r表示的正则模式中,对逗号进行的分组,然后后续可以在gensub的第二个参数位置使用&或\\NUM等。
#这里只引用第一个分组。所以结果中hello和word以及awk字符串没有打印。逗号被后面的\\1所引用了,所以有打印。
[root@node1 ~]# echo "hello,world\!hello,awk\!hello Linux\!"|awk 'BEGIN{r="[a-zA-Z]+(,)[a-zA-Z]+"}{print gensub(r,"\\1uj","g")}'
,uj\!,uj\!hello Linux\!
[root@node1 ~]# echo "hello,world\!hello,awk\!hello Linux\!"|awk 'BEGIN{r="[a-zA-Z]+(,)[a-zA-Z]+"}{print gensub(r,"\\1","g")}'
,\!,\!hello Linux\!
#gensub的第二个参数的&就表示r所匹配到的整体内容
[root@node1 ~]# echo "hello,world\!hello,awk\!hello Linux\!"|awk 'BEGIN{r="[a-zA-Z]+(,)[a-zA-Z]+"}{print gensub(r,"&uj","g")}'
hello,worlduj\!hello,awkuj\!hello Linux\!

(6) index(s, t)

返回字符串 t 第一次出现在字符串s位置时的索引值。如果没有匹配的,返回值为0(匹配的时候,索引从1开始)

index示例:

[root@node1 ~]# awk 'BEGIN{print index("lzolo,world","zo")}'
2
[root@node1 ~]# awk 'BEGIN{print index("lzolo,world","my")}'
0
[root@node1 ~]# awk 'BEGIN{print index("lzolo,world","m")}'
0
[root@node1 ~]# awk 'BEGIN{print index("lzolo,world","o")}'
3

(7) length([s])

返回指定字符串s的长度或者$0记录值的长度(省略字符串s)。作为非标准扩展,s参数传入一个数组,length()函数将会返回数组元素的个数。

(7) match(s, r [, a])

返回正则表达式r所匹配的部分在s中出现的位置(如果匹配不带,返回值为0),并且设置RSTART和RLENGTH内建变量的值。
注意:match()函数的参数出现的位置与~ 操作时类似的: str ~ re
如果提供了数组a,数组a的值首先会被清空,然后会向数组中填充值,r部分的正则表达式如果其中包含了子分组,用小括号
引用的子分组的正则表达式,那么数组a的元素从1开始到n会被这个子分组所匹配的内容填充,如果有1个字分组,如果有匹配
到内容,那么就会设置a[1]的值为子分组所匹配的内容。a[0]会填充整个正则表达式r所匹配的内容。
a[n,"start"]和a[n,"length"]提供了每个匹配的子串在整个匹配的整串中的相对开始位置和相对长度。

PS:这个特别是后边一部分理解起来有点复杂,是为了应对正则表达式中的分组的概念。
参考:
https://www.cnblogs.com/timeisbiggestboss/p/7242351.html
假设文本的内容为:
this is wang,not wan
that is chen,not che
this is chen,and wang,not wan che

awk '{match($0,/^.+is([^,]+).+not(.+)/,a);print a[0],a[1],a[2]}' file
$0是s参数的值,表示输入记录,默认是一行一行的;
/^.+is([^,]+).+not(.+)/ 是一个正则表达式部分,其中子分组有两个,一个是([^,]+),另外一个是(.+);
a表示第三个参数,就是一个数组。

整个正则表达式能够匹配到内容,两个子分组也能够匹配到内容。
第一行,$0的值为"this is wang,not wan",整个正则表达式所匹配的值为"this is wang,not wan",第一个分组匹配的值为" wang",第二个分组匹配的值为" wan"
数组a元素a[0]="this is wang,not wan"",a[1]=" wang",a[2]=" wan"

第二行,$0的值为"that is chen,not che",整个正则表达式所匹配的值为"that is chen, not che",第一个分组匹配的值为" chen",第二个分组匹配的值为" che"
数组a元素a[0]="this is wang,not wan"",a[1]=" chen",a[2]=" che"

第三行,$0的值为"this is chen,and wang,not wan che",整个正则表达式所匹配的值为"this is chen,and wang,not wan che",第一个分组匹配的值为" chen",第二个分组匹配的值为" wan che"
数组a元素a[0]="this is wang,not wan"",a[1]=" chen",a[2]=" wan che"

(8) patsplit(s, a [, r [, seps] ])

使用r(支持正则表达式)来分割字符串s,被正则表达式r所匹配到的分隔符会记录在数组a中,第一个元素从下标1开始。
返回值为分割后的字段的数量。seps[i]是出现在分隔符a[i+]之前的字段的值。如果r省略(seps也会省略),会使用
内建变量FPAT的值来作为字段分割符。这里的分隔与上文中讲到的字段分割类似。

示例:
   echo 11-22+33*44|awk '{patsplit($0,a,"[-+*]",b)}'
  i为0时候,a[1]的值为"[-+*]"所匹配到的字段分割符"-",$0中通过"-"分割后,分隔符之前的字段内容为11,所b[0]为11
  依次类推,a[2]的值为"+",b[1]的值为22;a[3]的值为"*",b[2]的值为33。最后一个字段的44值也会记录到b[3]

(9) split(s, a [, r [, seps] ])

使用r(支持正则表达式)来分隔字符串s,并把分隔后的结果保存到数组a中,整个函数的返回值为被r分隔的字段的数量。
如果r省略,将会使用FS的值来分隔s。数组a和数组seps在第一次使用的时候会被清空掉。
seps这个数组是用来存储分隔符的,记录的是r匹配到的分隔符,如果r省略,seps也会省略。
a[i]和a[a+i]是被分隔符seps[i]所分隔的两个字段的值。

如果r是一个单独的空格的时候,s中的前导空格或空白会被记录到seps[0]中,结尾部分的空格或空白会被记录到数组
seps[n]中,其中n是函数的返回值,也就是说s被r分隔的字段数量。
split函数的分隔行为和字段分隔类似的。

(10) sprintf(fmt, expr-list)

根据fmt格式打印表达式列表,并且返回最终的字符串结果。

(11) strtonum(str)

 检测str,并返回其(十进制)的数值。如果str以前导0开始,strtonum()会吧str当作八进制数字。如果str以前导0x或0X开始,
 strtonum()会把str当作16进制数字。如果str是一个字符串,返回值永远为0。其他情况,str统一被当作10进制处理。

(12) substr(s, i [, n])

返回字符串s中从第i个位置开始至多n个字符的子字符串。如果n省略, 从第i个位置开始到s余下的部分。
简单来说,就是字符串s,我可以截取从第i个字符开始往后的最多n个字符,如果没有指定字符的数量n,
将会截取从第i个字符开始到s字符串的结尾之间的所有字符串。字符串位置索引从1开始,如果i的值为0或者负数,统一视为i为1。

(13) tolower(str)

复制str的字符串的值,并把字符串中所有大写字母转成小写字母,并返回整个转换后的字符串。字符串中非字母字符不会转变。

(14) toupper(str)

复制str的字符串的值,并把字符串中所有小写字母转成大写字母,并返回整个转换后的字符串。字符串中非字母字符不会转变。

6.1.3、时间函数(了解即可,我不举例子了)

(1) mktime(datespec)
生成时间格式。

(2) strftime([format [, timestamp[, utc-flag]]])
格式化时间输出,将时间戳根据指定格式转为时间字符串。

(3) systime()
打印当前系统时间距离unix元年之间的秒数。等价于date +%s的值。

6.1.4、(二进制)位操作函数

(1) and(v1, v2)
返回v1和v2 值二进制形式按位与运算后的结果,结果是10进制。

(2) compl(val)
返回对val按位求的补码

(3) lshift(val, count)
返回val左移count位后的值;

(4) or(v1, v2)
返回v1和v2值的二进制形式按位或运算后的结果,结果是10进制。

(5) rshift(val, count)
返回val右移count位后的值;

(6) xor(v1, v2)
返回v1和v2值二进制形式按位异或运算后的结果,结果是10进制。

6.1.5、类型(判断)函数

isarray(x)
如果x是一个数组,返回值为真,否则为假。

6.1.6、国际化函数

ps:这类函数比较生僻,这里就不列举了。

6.2、自定义函数

自定义函数语法:

function name(parameter list) { statements }
函数名(函数参数列表) {函数语句部分}

函数可以在模式或者处理动作中的表达式部分调用并执行函数。在调用函数的时候,实际传参用于实例化函数中声名的形参部分。数组可以通过引用传递,其他变量按值传递。
因为早期函数不是awk语言的一部分,所以局部变量的提供(定义)相当的不灵活:它们被作为额外的参数在函数定义参数列表中申明。使用惯例是在函数形参列表中使用额外的空格来分隔局部变量和其他非局部变量。

function	f(p, q,	    a, b)   # a and b are local   #这里的a和b是局部变量
{
 ...
}

/abc/	{ ... ; f(1, 2) ; ... }

在函数调用的时候,函数名和小括号之间不能有空格,调用形式类似于function_name(parameterlist…)之所以这样规定,是为了避免与其他连接操作混淆语法。这个现在是awk的函数中不适用。函数可以相互调用并且支持递归调用。用作局部变量的函数参数在函数调用时初始化为空字符串和零号。return expr语句可以返回一个值。如果没有显式提供,这个返回的值是未定义的或者函数调用结束。作为gawk的扩展,函数可以被间接调用。在调用函数的时候,就像赋值字符串一样,直接赋值函数的名字给一个变量。然后使用这个变量就像它是函数的名字一样,需要在变量的签名加上符号@.

function	myfunc()
{
 print "myfunc called"
 ...
}

{	   ...
 the_func = "myfunc"
 @the_func()	  # call through the_func to myfunc
 ...
}

自定义函数示例:

[root@node1 ~]# cat max.awk 
#!/usr/bin/awk -f

function max(n1,n2)
{
    if(n1>n2) {return n1}
    else {return n2}
}
BEGIN {
   print max(11,2)
}
[root@node1 ~]# ls -l max.awk 
-rwxr--r--. 1 root root 119 Nov 30 23:18 max.awk

参考和引用:

这里是引用
awk博客笔记引用:
https://www.gnu.org/software/gawk/manual/gawk.html
http://www.runoob.com/w3cnote/awk-work-principle.html
http://wiki.jikexueyuan.com/project/awk/workflow.html
https://gist.github.com/harryxu/798150/9d5971bd88d72579c415a5a5c06b5c06d42831f7
http://net.pku.edu.cn/~yhf/tutorial/awk_manual.html
http://wiki.jikexueyuan.com/project/awk/overview.html
http://linux.51yip.com/search/awk
https://coolshell.cn/articles/9070.html
http://scc.qibebt.cas.cn/docs/linux/script/awk_use.pdf
http://www.aslibra.com/doc/awk.htm
http://blog.chinaunix.net/uid-10540984-id-371876.html
http://blog.chinaunix.net/uid-29529406-id-4808894.html
http://bbs.chinaunix.net/thread-2309494-1-1.html

### awk 命令详解及使用示例 #### 基本介绍 `awk` 是一种专为处理结构化文本数据而设计的编程语言和文本处理工具,其名称来源于三位创始人 Alfred Aho、Peter Weinberger 和 Brian Kernighan 的姓氏首字母。它广泛用于日志分析、数据提取、报表生成等场景,尤其擅长按字段处理文本数据并执行复杂的模式匹配和计算操作。 `awk` 的核心功能包括: - **文本分析**:支持按列或字段处理结构化文本数据。 - **模式匹配**:根据指定条件筛选文本数据。 - **数据处理**:支持算术运算和字符串操作。 - **报表生成**:可以格式化输出结果,生成简洁明了的报告。 #### 工作原理 `awk` 以逐行方式读取输入,将每一行按指定分隔符(默认为空格)拆分为字段,然后对每一行应用用户定义的模式匹配和操作规则,最后输出处理结果。这种机制使得 `awk` 在处理表格型数据时非常高效。 #### 内置变量 `awk` 提供了多个内置变量,用于简化文本处理任务,包括但不限于: - `$0`:表示当前整行内容。 - `$1`, `$2`, ...:分别表示第 1 个、第 2 个字段。 - `NF`:表示当前行的字段数量。 - `$NF`:表示最后一个字段。 - `NR`:表示当前处理的行号。 - `FNR`:表示当前文件中的行号(适用于多文件处理)。 - `FS`:输入字段分隔符,默认为空白字符。 - `OFS`:输出字段分隔符。 - `RS`:输入记录分隔符,默认为换行符。 - `ORS`:输出记录分隔符。 - `FILENAME`:表示当前输入文件名。 #### 常用选项 `awk` 支持多种命令行选项,常见的包括: - `-F 分隔符`:指定输入字段分隔符。 - `-v var=值`:设置变量值。 - `-f 脚本文件`:从文件中读取 `awk` 脚本。 #### 程序结构 `awk` 程序通常由模式和动作组成,其基本结构为: ```awk /pattern/ { action } ``` 其中,`pattern` 是可选的,用于筛选匹配的行;`action` 是对匹配行执行的操作,如打印字段、计算数值等。 #### 使用示例 ##### 示例 1:打印特定字段 假设 `data.txt` 文件内容如下: ``` Alice 25 Female Bob 30 Male Charlie 22 Female ``` 使用以下命令可以提取姓名和性别字段: ```bash awk '{print $1, $3}' data.txt ``` 输出结果为: ``` Alice Female Bob Male Charlie Female ``` ##### 示例 2:使用自定义字段分隔符 若文件使用逗号作为字段分隔符,例如 `csv_data.txt` 内容如下: ``` name,age,gender Alice,25,Female Bob,30,Male ``` 可以使用 `-F` 指定分隔符并打印姓名字段: ```bash awk -F ',' '{print $1}' csv_data.txt ``` 输出结果为: ``` name Alice Bob ``` ##### 示例 3:在脚本中使用数组 以下示例演示如何在 `awk` 中使用数组处理字符串: ```bash echo " " | awk '{ arrStr = "hello\nworld" split(arrStr, arr, "\n") for (k in arr) { print k, arr[k] } }' ``` 该脚本将字符串按换行符分割并存储到数组中,然后遍历数组输出索引和值。 ##### 示例 4:统计字段数量 使用 `NF` 可以统计每行的字段数量: ```bash awk '{print NF}' data.txt ``` 输出结果为: ``` 3 3 3 ``` ##### 示例 5:按行号筛选 使用 `NR` 可以筛选特定行号的内容: ```bash awk 'NR == 2' data.txt ``` 输出结果为: ``` Bob 30 Male ``` #### 进阶用法 ##### 条件判断 `awk` 支持条件判断语句,例如筛选年龄大于 25 的记录: ```bash awk '$2 > 25 {print $1}' data.txt ``` 输出结果为: ``` Bob ``` ##### 数值计算 可以对字段进行算术运算,例如计算总年龄: ```bash awk '{sum += $2} END {print sum}' data.txt ``` 输出结果为: ``` 77 ``` ##### 格式化输出 使用 `printf` 可以格式化输出内容: ```bash awk '{printf "Name: %s, Age: %d\n", $1, $2}' data.txt ``` 输出结果为: ``` Name: Alice, Age: 25 Name: Bob, Age: 30 Name: Charlie, Age: 22 ``` #### 总结 `awk` 是一个功能强大且灵活的文本处理工具,适用于结构化文本数据的分析和处理。通过内置变量、条件判断、循环结构和函数,可以实现复杂的文本操作任务。结合命令行选项和脚本编写,`awk` 在日志分析、数据清洗和报表生成等领域具有广泛的应用价值。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值