十五 awk文本处理

Awk 语法和基础命令

 以行为处理单位 对数据进行逐行处理 处理完当前行,把当前行的处理结果输出后自动对下一行进行处理 直到文件中所有行处理完为止

创造者:Aho、Weinberger、Kernighan
基于模式匹配检查输入文本,逐行处理并输出
通常用在Shell脚本中,获得指定的数据
单独用时,可对文本数据做统计

下面是 AWK 的几个变种:

AWK 是最原始的 AWK。
NAWK 是 new AWK
GAWK 是 GNU AWK。所有 linux 发行版都默认使用 GAWK,它和 AWK 以及 NAWK完全兼容

本书示例将用到下面三个文件,请先建立它们,然后用它们来运行所有示例。
employee.txt 文件
employee.txt 文件以逗号作为字段分界符,包含 5 个雇员的记录

[root@ceph-node5 ~]# vim employee.txt
101,John Doe,CEO
102,Jason Smith,IT Manager
103,Raj Reddy,Sysadmin
104,Anand Ram,Developer
105,Jane Miller,Sales Manager

tems.txt 文件
items.txt 是一个以逗号作为字段分界符的文本文件,包含 5 条记录

[root@ceph-node5 ~]# vim items.txt
101,HD Camcorder,Video,210,10
102,Refrigerator,Appliance,850,2
103,MP3 Player,Audio,270,15
104,Tennis Racket,Sports,190,20
105,Laser Printer,Office,475,5

items-sold.txt 文件

items-sold.txt 是一个以空格作为字段分界符的文本文件, 包含 5 条记录。每条记录都是特定
商品的编号以及当月的销售量(6 个月)。因此每条记录有 7 个字段。 第一个字段是商品编号,
第二个字段到第七个字段是 6 个月内每月的销售量。

[root@ceph-node5 ~]# vim items-sold.txt
101 2 10 5 8 10 12
102 0 1 4 3 0 2
103 10 6 11 20 5 13
104 2 3 4 0 6 5
105 10 2 5 7 12 6

语法格式

Awk –Fs '/pattern/{action}' input-file(或者)Awk –Fs '{action}' input-file

 上面语法中:

-F 为字段分界符。如果不指定,默认会使用空格作为分界符。
/pattern/和{action}需要用单引号引起来。
/pattern/是可选的。如果不指定, awk 将处理输入文件中的所有记录。如果指定一个模式, awk 则只处理匹配指定的模式的记录。
{action} 为 awk 命令,可以是单个命令,也可以多个命令。整个 action(包括里面的所有命令)都必须放在{ 和 }之间。
Input-file 即为要处理的文件

下面是一个演示 awk 语法的非常简单的例子:

[root@ceph-node5 ~]# awk -F: '/mail/ {print $1}' /etc/passwd
mail



这个例子中:

-F 指定字段分界符为冒号,即各个字段以冒号分隔。请注意,你也可以把分界符用双引号引住, -F":"也是正确的。
/mail/ 指定模式, awk 只会处理包含关键字 mail 的记录
{print $1} 动作部分,该动作只包含一个 awk 命令,它打印匹配 mail 的每条记录的第 1 个字段
/etc/passwd 即是输入文件

awk 命令放入单独的文件中(awk 脚本)

当需要执行很多 awk 命令时, 可以把/pattern/{action}这一部分放到单独的文件中,然后调用它:

awk –Fs –f myscript.awk input-file

myscript.awk 可以使用任意扩展名(或者不用扩展名)。但是加上扩展名.awk 便于维护,也可以在这个文件中设置字段分界符(后面详述),然后调用:

awk –f myscript.awk input-file

Awk 程序结构(BEGIN,body,END)区域

典型的 awk 程序包含下面三个区域:

 

1. BEGIN 区域

 

BEGIN { awk-commands }

BEGIN 区域的命令只最开始、在 awk 执行 body 区域命令之前执行一次。

BEGIN 区域很适合用来打印报文头部信息,以及用来初始化变量。
 BEGIN 区域可以有一个或多个 awk 命令
 关键字 BEGIN 必须要用大写
 BEGIN 区域是可选的

2.body 区域


body 区域的语法:

/pattern/ {action}


body 区域的命令每次从输入文件读取一行就会执行一次

如果输入文件有 10 行,那 body 区域的命令就会执行 10 次(每行执行一次)
Body 区域没有用任何关键字表示,只有用正则模式和命令。

3. END block


END 区域的语法:
END { awk-commands }
END 区域在 awk 执行完所有操作后执行,并且只执行一次。

END 区域很适合打印报文结尾信息,以及做一些清理动作
END 区域可以有一个或多个 awk 命令
关键字 END 必须要用大写
END 区域是可选的

如图:


下面的例子包含上上述的三个区域:

[root@ceph-node5 ~]#  awk 'BEGIN { FS=":";print "----header----" } 
> /mail/ {print $1} 
> END {print "----footer----"}' /etc/passwd
----header----
mail
----footer----


提示:如果命令很长,即可以放到单行执行,也可以用折成多行执行。上面的例子用把命令折成了 3 行。
在这个例子中:
 

BEGIN { FS=”:”;print “----header----“ } 为 BEGIN 区域,它设置了字段分界符变量 FS(下文详述)的值,然后打印报文头部信息。
这个区域仅在 body 区域循环之前执行一次。
/mail/{print $1}是 body 区域,包含一个正则模式和一个动作,即在输入文件中搜索包含关键字 mail 的行,并打印第一个字段。 END {print “----footer----“ }是 END 区域,打印报文尾部信息。 /etc/passwd 是输入文件,每行记录都会执行一次 body 区域里的动作。

上面的例子中,除了可以在命令行上执行外,还可以通过脚本执行。
首先建立下面的文件 myscript.awk,它包含了 begin,body end

[root@ceph-node5 ~]# vim myscript.awk
BEGIN {
FS=":"
print "---header---"
}
/mail/ {
print $1
}
END {
print "---footer---"
}




然后,如下所示,在/etc/passwd 上执行 myscript.awk 文件:

[root@ceph-node5 ~]# awk -f myscript.awk /etc/passwd
---header---
mail
---footer---



请注意, awk 脚本中,注释以#开头。 如果要编写复杂的 awk 脚本,最后接受下面的建议:
*awk 文件中写上足够多的注释,这样以后再次使用该脚本时,更易于读懂。
下面是随机列出的一些简单的例子,用例演示 awk 各个区域的不同组合方式:
只有 body 区域:

awk -F: '{ print $1 }' /etc/passwd


同时具有 begin,body end 区域:

awk -F: 'BEGIN{printf "username
-------
"}{ print $1 }END {print "----------" }' /etc/passwd 


只有 begin body 区域:

awk -F: 'BEGIN {print "UID"} {print $3}' /etc/passwd 


关于使用 BEGIN 区域的提示:
只使用 BEGIN 区域在 awk 中是符合语法的。在没有使用 body 区域时,不需要指定输入文件,因为 body 区域只在输入文件上执行。

所以在执行和输入文件无关的工作时,可以只使用BEGIN 区域。 下面的不少例子中,只包含 BEGIN 区域,用来说明 awk 的不同部分是如何执行的。

你可以因地制宜地使用下面的例子。

只包含 BEGIN 的简单示例:

[root@ceph-node5 ~]# awk 'BEGIN { print "Hello,World!" }'
Hello,World!



多个输入文件:
注意,可以为 awk 指定多个输入文件。 如果指定了两个文件,那么 body 区域会首先在第一个文件的所有行上执行,然后在第二个文件的所有行上执行。
多个输入文件示例:

[root@ceph-node5 ~]# awk 'BEING { FS=":";print "---header---" } /mail/ {print $1}
> END { print "---footer---"}' /etc/passwd /etc/group
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
mail:x:12:postfix
---footer---


注意,即是指定了多个文件, BEGIN END 区域,仍然只会执行一次。

打印命令

默认情况下, awk 的打印命令 print(不带任何参数)会打印整行数据。下面的例子等价于cat employee.txt命令

[root@ceph-node5 ~]# awk '{print}' employee.txt
101,John Doe,CEO
102,Jason Smith,IT Manager
103,Raj Reddy,Sysadmin
104,Anand Ram,Developer
105,Jane Miller,Sales Manager

也可以通过传递变量“$字段序号作为 print 的参数来指定要打印的字段。 我们猜想例子应该只打印雇员名称(2 个字段)

[root@ceph-node5 ~]# awk '{print $2}' employee.txt
Doe,CEO
Smith,IT
Reddy,Sysadmin
Ram,Developer
Miller,Sales

等等,这个输出好像和预期不符。 它打印了从姓氏开始直到记录结尾的所有内容。 这是因为awk 默认的字段分隔符是空格, awk 准确地执行了我们要求的动作,它以空格作为分隔符,打印第 2 个字段。当使用默认的空格作为字段分隔符时, 101,Johne 变成了第一条记录的第一个字段, Doe,CEO 变成了第二个字段。因此上面例子中, awk Doe,CEO 作为第二个字段打印出来了

要解决这个文件,应该使用-F 选项为 awk 指定一个逗号,最为字段分隔符。

[root@ceph-node5 ~]# awk -F ',' '{print $2}' employee.txt
John Doe
Jason Smith
Raj Reddy
Anand Ram
Jane Miller

当字段分隔符是单个字符时,下面的所有写法都是争取的,即可以把它放在单引号或双引号中,或者不使用引号:

awk –F ',' '{print $2}' employee.txt
awk –F "," '{print $2}' employee.txt
awk –F , '{print $2}' employee.txt


提示:也可使用 FS 变量来达到同样的目的。后面会介绍这个 awk 内置变量的用法。
一个简单的例子,用来输出雇员姓名,职位,同时附带 header footer 信息:

[root@ceph-node5 ~]# awk 'BEGIN{FS=",";print "---------
Name Title
------------
";}{print $2,"	",$3}END {print "-------------------"}' employee.txt
---------
Name Title
------------

John Doe         CEO
Jason Smith      IT Manager
Raj Reddy        Sysadmin
Anand Ram        Developer
Jane Miller      Sales Manager
-------------------

这个例子中,输出结果各字段并没有很好地对齐,后面章节将会介绍如何处理这个问题。 这
个例子还展示了如何使用 BEGIN 来打印 header 以及如何使用 END 来打印 footer.
请注意, $0 代表整条记录。 下面两个命令是等价的,都打印 employee.txt 的所有行:

awk '{print}' employee.txt
awk '{print $0}' employee.txt 

 

模式匹配


你可以只在匹配特殊模式的行数执行 awk 命令。
下面的例子只打印管理者的姓名和职位:

[root@ceph-node5 ~]# awk -F ',' '/Manager/ {print $2,$3}' employee.txt
Jason Smith IT Manager
Jane Miller Sales Manager



下面的例子只打印雇员 id 102 的雇员的信息:

[root@ceph-node5 ~]# awk -F ',' '/^102/{print "Emp id 102 is",$2}'  employee.txt 
Emp id 102 is Jason Smith

awk内置变量

直接含义,可直接使用

调用变量的时候不用$符号标示,直接调用就可以

变量        用途
FILENAME     当前处理文件的文件名
$0         当前读入的整行文本内容
NR         记录当前已读入行的数量(行数)
FNR        保存当前处理行在原文本内的序号(行号)
NF         记录当前处理行的字段个数(列数)
$n        指定分隔的第n个字段,如$1、$3分别表示第1、第3列
FS        保存或设置字段分隔符,例如FS=":"
ENVIRON     调用Shell环境变量,格式为:ENVIRON["变量名"]

FS –输入字段分隔符

 wk 默认的字段分隔符是空格,如果你的输入文件中不是一个空格作为字段分隔符, 你已经知道可以在 awk 命令行上使用-F 选项来指定它:

[root@ceph-node5 ~]# awk -F ',' '{print $2,$3}' employee.txt 
John Doe CEO
Jason Smith IT Manager
Raj Reddy Sysadmin
Anand Ram Developer
Jane Miller Sales Manager


同样的事情,也可以使用 awk 内置变量 FS 来完成。 FS 只能在 BEGIN 区域中使用。

[root@ceph-node5 ~]# awk 'BEGIN {FS=","} {print $2,$3}' employee.txt    
John Doe CEO
Jason Smith IT Manager
Raj Reddy Sysadmin
Anand Ram Developer
Jane Miller Sales Manager


BEGIN区域可以包含多个命令,下面的例子中, BEGIN区域包含一个FS和一个print命令.BEGIN
区域的多个命令之间,要用分号分隔。

awk 'BEGIN { FS=",";
print "---------------------------
Name	Title
------------------------"}
{print $2,"	",$3;}
END {print "-----------------------------------------"}' employee.txt 

结果:

[root@ceph-node5 ~]# awk 'BEGIN { FS=",";
> print "---------------------------
Name	Title
------------------------"}
> {print $2,"	",$3;}
> END {print "-----------------------------------------"}' employee.txt 
---------------------------
Name    Title
------------------------
John Doe         CEO
Jason Smith      IT Manager
Raj Reddy        Sysadmin
Anand Ram        Developer
Jane Miller      Sales Manager
-----------------------------------------

注意:默认的字段分隔符不仅仅是单个空格字符,它实际上是一个或多个空白字符。
下面的 employee-multiple-fs.txt 文件,每行记录都包含 3 个不同的字段分隔符:

, 雇员 id 后面的分隔符是逗号
: 雇员姓名后面的分隔符是分号
% 雇员职位后面的分隔符是百分号

创建文件:

[root@ceph-node5 ~]# vim employee-multiple-fs.txt
101,John Doe:CEO%10000
102,Jason Smith:IT Manager%5000
103,Raj Reddy:Sysadmin%4500
104,Anand Ram:Developer%4500
105,Jane Miller:Sales Manager%3000 

当遇到一个包含多个字段分隔符的文件时,不必担心, FS 可以搞定。 你可以使用正则表达
式来指定多个字段分隔符,如 FS = “[,:%]” 指定字段分隔符可以是逗号 ,或者分号 : 或者百分号 %
因此,下面的例子将打印 employee-multiple-fs.txt 文件中雇员名称和职位

[root@ceph-node5 ~]#  awk 'BEGIN {FS="[,:%]"}{print $2,$3}' employee-multiple-fs.txt
John Doe CEO
Jason Smith IT Manager
Raj Reddy Sysadmin
Anand Ram Developer
Jane Miller Sales Manager

OFS – 输出字段分隔符


FS 是输入字段分隔符, OFS 是输出字段分隔符。 OFS 会被打印在输出行的连续的字段之间。
默认情况下, awk 在输出字段中间以空格分开。
请注意,我们没有指定 IFS 作为输入字段分隔符,我们从简地使用 FS
下面的例子打印雇员姓名和薪水,并以空格分开。当你使用单个 print 语句打印多个以逗号
分开(如下面的例子所示)的变量是,每个变量之间会以空格分开。

[root@ceph-node5 ~]# awk -F ',' '{print $2,$3}' employee.txt 
John Doe CEO
Jason Smith IT Manager
Raj Reddy Sysadmin
Anand Ram Developer
Jane Miller Sales Manager


如果你尝试认为地在输出字段之间加上冒号,会有如下输出。请注意在冒号前后均有一个多余的空格,这是因为 awk 仍然以空格作为输出字段分隔符。
下面的 print 语句实际上会打印 3 个值(以逗号分割)——$2,:$4.因为如你所知,当使用单个print 语句打印多个变量时,输出内容会包含多余的空格。

[root@ceph-node5 ~]# awk -F ',' '{print $2,":",$3}' employee.txt
John Doe : CEO
Jason Smith : IT Manager
Raj Reddy : Sysadmin
Anand Ram : Developer
Jane Miller : Sales Manager



正确的方法是使用 awk 内置变量 OFS(输出字段分隔符),如下面了示例。 请注意这个例子中分号前后没有多余的空格,因为 OFS 使用冒号取代了 awk 默认的分隔符。
下面的 print 语句打印两个变量($2 $4),使用都会分隔,然而输出结果却是以分号分隔(而不是空格),因为 OFS 被设置成了分号.

[root@ceph-node5 ~]# awk -F ',' 'BEGIN {OFS=":"} {print $2,$3}' employee.txt 
John Doe:CEO
Jason Smith:IT Manager
Raj Reddy:Sysadmin
Anand Ram:Developer
Jane Miller:Sales Manager

同时请注意在 print 语句中使用和不使用逗号的细微差别(打印多个变量时).当在 print 语句中指定了逗号, awk 会使用 OFS。如下面的例子所示,默认的 OFS 会被使用,所以你会看到输出值之间的空格。

[root@ceph-node5 ~]# awk 'BEGIN { print "test1","test2"}'
test1 test2


不使用逗号是, awk 将不会使用 OFS,其输出变量之间没有任何空格。

[root@ceph-node5 ~]# awk 'BEGIN { print "test1" "test2"}'
test1test2

RS – 记录分隔符

假定有下面一个文件,雇员的 id 和名称都在单一的一行内.

[root@ceph-node5 ~]# vim employee-one-line.txt
101,John Doe:102,Jason Smith:103,Raj Reddy:104,Anand Ram:105,Jane, Miller


这个文件中,每条记录包含两个字段(empid name),并且每条记录以分红分隔(取代了换行符).每条记录中的单独的字段(empid name)以逗号分隔。
Awk 默认的记录分隔符是换行符。如果要尝试只打印雇员姓名,下面的例子无法完成:

[root@ceph-node5 ~]#  awk -F, '{print $2}' employee-one-line.txt
John Doe:102


这个例子把 employee-one-line.txt 的内容作为单独一行,把逗号作为字段分隔符,所以,它打印”John Doe:102 作为第二个字段。
如果要把文件内容作为 5 行记录来处理(而不是单独的一行), 并且打印每条记录中雇员的姓名,就必须把记录分隔符指定为分号,如下所示:

[root@ceph-node5 ~]# awk -F, 'BEGIN { RS=":" } {print $2}' employee-one-line.txt
John Doe
Jason Smith
Raj Reddy
Anand Ram
Jane


假设有下面的文件,记录之间用-分隔,独占一行。所有的字段都占单独的一行。

[root@ceph-node5 ~]# vim employee-change-fs-ofs.txt
101
John Doe
CEO
-
102
Jason Smith
IT Manager
-
103
Raj Reddy
Sysadmin
-
104
Anand Ram
Developer
-
105
Jane Miller
Sales Manager

上面例子中,字段分隔符 FS 是换行符,记录分隔符 RS ”-“和换行符。所以如果要打印雇员
名称和职位,需要:

[root@ceph-node5 ~]# awk 'BEGIN{RS="-
";FS="
";OFS=":"}{print $2,$3}' employee-change-fs-ofs.txt
John Doe:CEO
Jason Smith:IT Manager
Raj Reddy:Sysadmin
Anand Ram:Developer
Jane Miller:Sales Manager 



ORS – 输出记录分隔符


RS 是输入字段分隔符, ORS 是输出字段分隔符。请注意,我们没有指定 IFS 作为输入字段分隔符,我们从简地使用 FS
下面的例子在每个输出行后面追加---------。wk默认使用换行符” ”作为ORS,这个例子中,我们使用" ---"作为 ORS

[root@ceph-node5 ~]# awk 'BEGIN {FS=",";ORS="
---
"} {print $2,$3}' employee.txt
John Doe CEO
---
Jason Smith IT Manager
---
Raj Reddy Sysadmin
---
Anand Ram Developer
---
Jane Miller Sales Manager
---

下面的例子从 employee.txt 获取输入,把每个字段打印成单独一行,每条记录用”---“分隔

[root@ceph-node5 ~]# awk 'BEGIN { FS=",";OFS="
";ORS="
---
"}{print $1,$2,$3}' employee.txt
101
John Doe
CEO
---
102
Jason Smith
IT Manager
---
103
Raj Reddy
Sysadmin
---
104
Anand Ram
Developer
---
105
Jane Miller
Sales Manager
---

NR – 记录序号

NR 非常有用,在循环内部标识记录序号。用于 END 区域时,代表输入文件的总记录数。尽管你会认为 NR 代表记录的数量(Number of Records)”,但它跟确切的叫法是记录的序号(Number of the Record)”,也就是当前记录在所有记录中的行号。
下面的例子演示了 NR block END 区域是怎么运行的:

[root@ceph-node5 ~]# awk 'BEGIN {FS=","}{print "Emp Id of record number",NR,"is",$1;}END {print "Total number of records:",NR}' employee.txt 
Emp Id of record number 1 is 101
Emp Id of record number 2 is 102
Emp Id of record number 3 is 103
Emp Id of record number 4 is 104
Emp Id of record number 5 is 105
Total number of records: 5

 

FILENAME – 当前处理的文件名

当使用 awk 处理多个输入文件时, FILENAME 就显得很有用,它代表 awk 当前正在处理的文件。

[root@ceph-node5 ~]#  awk '{ print FILENAME }'  employee.txt employee-multiple-fs.txt 
employee.txt
employee.txt
employee.txt
employee.txt
employee.txt
employee-multiple-fs.txt
employee-multiple-fs.txt
employee-multiple-fs.txt
employee-multiple-fs.txt
employee-multiple-fs.txt

 

如果 awk 从标准输入获取内容, FILENAME 的值将会是”-“,下面的例中,我们不提供任何输入文件, 所以你应该手动输入内容以代替标准输入。
下面的例子中,我们只输入一个人名”John Doe”作为第一条记录,然后 awk 打印出该人的姓氏。
这种情况下,必须按 Ctrl-C 才能停止标准输入。

[root@ceph-node5 ~]# awk '{print "Last name:",$2;print "Filename:",FILENAME}'
John Deo       
Last name: Deo
Filename: -
^C



上面这个例子在使用管道向 awk 传递数据时,同样适用。 如下所示,打印出来的 FILENAME仍然是”-“

[root@ceph-node5 ~]# echo "Johe Doe" | awk '{print "Last name:",$2;print "Filename:",FILENAME}'
Last name: Doe
Filename: -


注意:BEGIN 区域内, FILENAME 的值是空,因为 BEGIN 区域只针对 awk 本身,而不处理任何文件。

FNR – 文件中的 NR

我们已经知道 NR 记录条数”(或者叫记录的序号”),代表 awk 当前处理的记录的行号。

在给 awk 传递了两个输入文件时 NR 会是什么? NR 会在多个文件中持续增加,当处理到第二个文件时, NR 不会被重置为 1,而是在前一个文件的 NR 基础上继续增加。下面的例子中,第一个文件有 5 条记录,第二个文件也有 5 条记录。如下所示,当 body 区域的循环处理到第二个文件时, NR 6 开始递增(而不是 1).最后在 END 区域, NR 返回两个文件的总记录条数。

[root@ceph-node5 ~]#  awk 'BEGIN {FS=","}{print FILENAME ": record number",NR,"is",$1;} END {print "Total number of records:",NR}' employee.txt employee-multiple-fs.txt
employee.txt: record number 1 is 101
employee.txt: record number 2 is 102
employee.txt: record number 3 is 103
employee.txt: record number 4 is 104
employee.txt: record number 5 is 105
employee-multiple-fs.txt: record number 6 is 101
employee-multiple-fs.txt: record number 7 is 102
employee-multiple-fs.txt: record number 8 is 103
employee-multiple-fs.txt: record number 9 is 104
employee-multiple-fs.txt: record number 10 is 105
Total number of records: 10


下面例子同时打印 NR FNR:

cat fnr.awk
BEGIN{FS=","}{print "FILENAME="FILENAME,"NR="NR,"FNR="FNR}END{print "END Block:NR="NR,"FNR="FNR}
[root@ceph-node5 ~]#  awk -f fnr.awk employee.txt employee-multiple-fs.txt
FILENAME=employee.txt NR=1 FNR=1
FILENAME=employee.txt NR=2 FNR=2
FILENAME=employee.txt NR=3 FNR=3
FILENAME=employee.txt NR=4 FNR=4
FILENAME=employee.txt NR=5 FNR=5
FILENAME=employee-multiple-fs.txt NR=6 FNR=1
FILENAME=employee-multiple-fs.txt NR=7 FNR=2
FILENAME=employee-multiple-fs.txt NR=8 FNR=3
FILENAME=employee-multiple-fs.txt NR=9 FNR=4
FILENAME=employee-multiple-fs.txt NR=10 FNR=5
END Block:NR=10 FNR=5

awk 变量的操作符

变量


Awk 变量以字母开头,后续字符可以是数字、字母、或下划线。关键字不能用作 awk 变量。
不像其他编程语言, awk 变量可以直接使用而不需事先声明。 如果要初始化变量,最好在BEGIN 区域内作,它只会执行一次。
Awk 中没有数据类型的概念,一个 awk 变量是 number 还是 string 取决于该变量所处的上下文。
employee-sal.txt 示例文件
emploeyee-sal.txt 以逗号作为字段分隔符,包含五个雇员的信息,格式如下:

employee-numer,employee-name,employee-title,salary 

创建下面文件:

[root@ceph-node5 ~]# vim employee-sal.txt
101,John Doe,CEO,10000
102,Jason Smith,IT Manager,5000
103,Raj Reddy,Sysadmin,4500
104,Anand Ram,Developer,4500
105,Jane Miller,Sales Manager,3000


下面的例子将教你如何在 awk 中创建和使用自己的变量, 该例中, ”total”便是用户建立的用
来存储公司所有雇员工资总和的变量。

[root@ceph-node5 ~]# vim total-company-salary.awk
BEGIN {
FS=",";
total=0;
} {
print $2 "'s slary is: " $4;
total=total+$4
}
END {
print "---
Total company salary =$"total;
}
 
[root@ceph-node5 ~]# awk -f total-company-salary.awk employee-sal.txt
John Doe's slary is: 10000
Jason Smith's slary is: 5000
Raj Reddy's slary is: 4500
Anand Ram's slary is: 4500
Jane Miller's slary is: 3000
---
Total company salary =$27000

一元操作符

只接受单个操作数的操作符叫做一元操作符

下面的例子使用取反操作:

[root@ceph-node5 ~]# awk -F, '{print -$4}' employee-sal.txt
-10000
-5000
-4500
-4500
-3000

下面的例子演示取正、取反操作符对文件中存放的复数的作用:

[root@ceph-node5 ~]# vim negative.txt
-1
-2
-3
[root@ceph-node5 ~]# awk '{print +$1}' negative.txt
-1
-2
-3
[root@ceph-node5 ~]#  awk '{print -$1}' negative.txt
1
2
3

 

自增和自减操作

 


自增和自减改变变量的值,它可以在使用变量“之前”或“之后”改变变量的值。 在表达式中,使用的可能是改变前的值(post)或改变后的值(pre).

使用改变后的变量值(pre)即是在变量前面加上++(--),首先把变量的值加 1(或减 1),然后把改变后的值应用到表达式的其它操作中。
使用改变前的变量值(post)即是在变量后面加上++(--),首先把变量值应用到表达式中进行
计算,然后把变量的值加 1(或减 1)
Pre 自增示例:

[root@ceph-node5 ~]# awk -F, '{print ++$4}' employee-sal.txt 
10001
5001
4501
4501
3001

Pre 自减示例:

[root@ceph-node5 ~]#  awk -F, '{print --$4}' employee-sal.txt
9999
4999
4499
4499
2999


Post 自增示例:
(因为++ print 语句中,所以变量的原始值被打印)

[root@ceph-node5 ~]# awk -F, '{print $4++}' employee-sal.txt
10000
5000
4500
4500
3000


Post 自减示例:
(因为++是单独语句,所以自增后的值被打印)

[root@ceph-node5 ~]# awk -F, '{$4++;print $4}' employee-sal.txt
10001
5001
4501
4501
3001


Post 自减示例:
(因为print 语句中,所以变量原始值被打印)

[root@ceph-node5 ~]# awk -F, '{print $4--}' employee-sal.txt
10000
5000
4500
4500
3000

Post 自减示例:
(因为在单独语句中,所以自减后的值被打印)

[root@ceph-node5 ~]# awk -F, '{$4--;print $4}' employee-sal.txt 
9999
4999
4499
4499
2999

下面是一个有用的例子,显示所有登录到 shell 的用户数,即哪些用户可以登录 shell 并获得命令提示符。

使用算后自增运算符(尽管变量值只到 END 区域才打印出来,算前自增仍会产生同样的结果)
脚本的 body 区域包含一个模式匹配,因此只有最后一个字段匹配模式/bin/bash 时,body 的代码才会执行
提示:正则表达式应该包含在//之间,但如果正则表达式中的/必须转移,以避免被解析为正则表达式的结尾
当有匹配到模式的行时,变量 n 的值增加 1,最终的值在 END 区域打印出来。

打印所有可登陆 shell 的用户总数:

awk -F':' '$NF ~ //bin/bash/ { n++ }; END { print n }' /etc/passwd

算术操作符

要两个操作数的操作符,成为二元操作符。 Awk 中有多种基本二元操作符(如算术操作符、
字符串操作符、赋值操作符,等等)
下面是算术运算操作符:

下面的例子展示+,-,*,/的用法

下面例子完成两个事情:

1. 将每件单独的商品价格减少 20%
2. 将每件单独的商品的数量减少 1 

创建并运行 awk 算术运算脚本:

[root@ceph-node5 ~]# vim arithmetic.awk
BEGIN {
FS=",";
OFS=",";
item_discount=0;
} {
item_discount=$4*20/100;
print $1,$2,$3,$4-item_discount,$5-1;
}
[root@ceph-node5 ~]# awk -f arithmetic.awk items.txt
101,HD Camcorder,Video,168,9
102,Refrigerator,Appliance,680,1
103,MP3 Player,Audio,216,14
104,Tennis Racket,Sports,152,19
105,Laser Printer,Office,380,4

下面的例子只打印偶数行。打印前会检查行号是否能被 2 整除,如果整除,则执行默认的操
(打印整行).
取模运算演示:

[root@ceph-node5 ~]# awk 'NR % 2 == 0' items.txt
102,Refrigerator,Appliance,850,2
104,Tennis Racket,Sports,190,20

字符串操作符

(空格)是连接字符串的操作符。下面例子中,有三处使用了字符串连接。在语句”string3=string1 string2”中, string3 包含了string1 string2 连接后的内容。每个 print 语句都把一个静态字符串和 awk 变量做了连接。提示:这个操作符解释了为什么在打印多个变量时, 如果要使用 OFS 分隔每个字段,就需要在 print 语句中用逗号分隔每个变量。如果没有使用逗号分隔,那么会把所有值连接成一个字符串。

[root@ceph-node5 ~]# vim  string.awk
BEGIN { FS
=","; OFS=","; string1="Audio"; string2="Video"; numberstring="100"; string3=string1 string2; print "Concatenate string is:" string3; numberstring=numberstring+1; print "String to number:" numberstring; }
[root@ceph-node5 ~]# awk -f string.awk items.txt
Concatenate string is:AudioVideo
String to number:101

赋值操作符

与其他大部分编程语言一样, awk 使用 = 作为赋值操作符。 和 C 语言一样, awk 支持赋值的缩写方式。

 下面的例子演示如何使用赋值:

[root@ceph-node5 ~]# vim assignment.awk
BEGIN {
FS=",";
OFS=",";
total1 = total2 = total3 = total4 = total5 = 10;
total1 += 5; print total1;
total2 -= 5; print total2;
total3 *= 5; print total3;
total4 /= 5; print total4;
total5 %= 5; print total5;
}
[root@ceph-node5 ~]#   awk -f assignment.awk
15
5
50
2
0

下面的例子使用加法赋值的缩写形式.
显示所有商品的清单:

[root@ceph-node5 ~]# awk -F ',' 'BEGIN { total=0 } { total += $5 } END {print "Total Quantity: " total }' items.txt
Total Quantity: 52


下面的例子统计输入文件中所有的字段数。 Awk 读取每一行,并把字段数量增加到变量 total
中。然后在 END 区域打印该变量。

[root@ceph-node5 ~]# awk -F ',' 'BEGIN { total = 0 } { total += NF } END { print total }' items.txt
25

 

比较操作符

 


Awk 支持下面标准比较操作符

 

操作符    含义
==    等于;用于字符比较时要加 "" 双引号 
!=    不等于;用于字符比较时要加 "" 双引号
>     大于
>=    大于或等于
<     小于
<=    小于或等于

提示:下面的例子,如果不指定操作, awk 会打印符合条件的整条记录。
打印数量小于等于临界值 5 的商品信息:

[root@ceph-node5 ~]# awk -F ',' '$5 <= 5' items.txt
102,Refrigerator,Appliance,850,2
105,Laser Printer,Office,475,5


打印编号为 103 的商品信息:

[root@ceph-node5 ~]# awk -F "," '$1 == 103' items.txt
103,MP3 Player,Audio,270,15


提示:不要把==(等于)=(赋值)搞混了。

打印编号为 103 的商品的描述信息:

[root@ceph-node5 ~]# awk -F "," '$1 == 103 { print $2}' items.txt
MP3 Player



打印除 Video 以外的所有商品:

[root@ceph-node5 ~]# awk -F "," '$3 != "Video"' items.txt
102,Refrigerator,Appliance,850,2
103,MP3 Player,Audio,270,15
104,Tennis Racket,Sports,190,20
105,Laser Printer,Office,475,5



和上面相同,但只打印商品描述信息:

[root@ceph-node5 ~]# awk -F "," '$3 != "Video" { print $2}' items.txt
Refrigerator
MP3 Player
Tennis Racket
Laser Printer


使用&&比较两个条件,打印价钱低于 900 并且数量小于等于临界值 5 的商品信息:

[root@ceph-node5 ~]# awk -F "," '$4 < 900 && $5 <= 5' items.txt
102,Refrigerator,Appliance,850,2
105,Laser Printer,Office,475,5


和上面相同,但只打印商品描述信息:

[root@ceph-node5 ~]# awk -F "," '$4 < 900 && $5 <= 5 {print $2}' items.txt
Refrigerator
Laser Printer


使用||比较两个条件,打印价钱低于 900 或者数量小于等于临界值 5 的商品信息:

[root@ceph-node5 ~]#  awk -F "," '$4 < 900 || $5 <= 5' items.txt
101,HD Camcorder,Video,210,10
102,Refrigerator,Appliance,850,2
103,MP3 Player,Audio,270,15
104,Tennis Racket,Sports,190,20
105,Laser Printer,Office,475,5


和上面相同,但只打印商品描述信息:

[root@ceph-node5 ~]# awk -F "," '$4 < 900 || $5 <= 5 {print $2}' items.txt
HD Camcorder
Refrigerator
MP3 Player
Tennis Racket
Laser Printer

下面例子使用>条件,打印/etc/password 中最大的 UID(以及其所在的整行)

Awk 把最大的UID(3 个字段)放在变量 maxuid 中,并且把包含最大 UID 的行复制到变量 maxline 中。

循环执行完后,打印最大的 UID 和其所在的行。

[root@ceph-node5 ~]#  awk -F ':' '$3 > maxuid { maxuid = $3; maxline = $0 } END { print maxuid,maxline }' /etc/passwd
999 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
[root@ceph-node5 ~]# 


打印/etc/passwd UID GROUP ID 相同的用户信息

[root@ceph-node5 ~]#  awk -F ':' '$3 == $4' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin
ceph:x:167:167:Ceph daemons:/var/lib/ceph:/sbin/nologin


打印/etc/passwd UID >= 100 并且用户的 shell /bin/sh 的用户:

awk -F ':' '$3 >= 100 && $NF ~ //bin/sh/ ' /etc/passwd


打印/etc/passwd 中没有注释信息(5 个字段)的用户:

[root@ceph-node5 ~]# awk -F ':' '$5 == ""' /etc/passwd
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin

正则表达式操作符

 

 ~匹配:结果匹配正则表达式为真 !~不匹配:结果不匹配正则表达式为真

使用==时, awk 检查精确匹配。 下面的例子不会打印任何信息,因为 items.txt 中,没有任何一条记录的第二个字段精确匹配关键字”Tennis”,”Tennis Racket”不是精确匹配。
打印第二个字段为”Tennis”的记录:

awk -F "," '$2 == "Tennis"' items.txt


当使用~时, awk 执行模糊匹配,检索包含关键字的记录.
打印第二个字段包含”Tennis”的行:

[root@ceph-node5 ~]# awk -F "," '$2 ~ "Tennis"' items.txt
104,Tennis Racket,Sports,190,20


!~ 对应 ~,即不匹配.
打印第二个字段不包含”Tennis”的行:

下面的例子打印 shell /bin/bash 的用户的总数,如果最后一个字段包含”/bin/bash”,则变量n 增加 1

[root@ceph-node5 ~]# awk -F ':' '$NF ~ //bin/bash/ { n++ } END { print n }' /etc/passwd 
1

 

 awk 分支和循环

Awk 支持条件判断,控制程序流程。 Awk 的大部分条件判断语句很像 C 语言的语法。
Awk 支持下面三种 if 语句.

单个 if 语句
if-else 语句
多级 if-elseif 语句

if 结构


单个 if 语句检测条件,如果条件为真,执行相关的语句

单条语句

语法:

if(conditional-expression )
action 

解析:

if 是关键字
conditional-expression 是要检测的条件表达式
action 是要执行的语句

多条语句


如果要执行多条语句,需要把他们放在{ } 中,每个语句之间必须用分号或换行符分开,如下所示.
语法:

if (conditional-expression)
{
action1;
action2;
}

如果条件为真, { }中的语句会依次执行。当所有语句执行完后, awk 会继续执行后面的语句。
打印数量小于等于 5 的所有商品:

[root@ceph-node5 ~]# awk -F ',' '{ if ($5 <= 5) print "Only",$5,"qty of",$2 "is available" }' items.txt
Only 2 qty of Refrigeratoris available
Only 5 qty of Laser Printeris available

使用多个条件,可以打印价钱在 500 100,并且总数不超过 5 的商品

[root@ceph-node5 ~]# awk -F ',' '{ if (($4 >= 500 && $4<= 1000) && ($5 <= 5)) print "Only",$5,"qty of",$2,"isavailable" }' items.txt
Only 2 qty of Refrigerator isavailable

if else 结构


if else 结构中, 还可以指定判断条件为 false 时要执行的语句。 下面的语法中,如果条件为 true,那么执行 action1,如果条件为 false,则执行 action2

语法:

if (conditional-expression)
  action1
else
  action2


此外, awk 还有个条件操作符( ? : ), 和 C 语言的三元操作符等价。

if-else 结构相同,如果 codintional-expresion true,执行 action1,否则执行 action2
三元操作符:

codintional-expression ? action1 : action2 ;


如果商品数量不大于 5,打印”Buy More”,否则打印商品数量

[root@ceph-node5 ~]# vim if-else.awk
BEGIN {
FS=",";
} {
if( $5 <= 5)
print "Buy More: Order",$2,"immediately!"
else
print "Shell More: Give discount on",$2,"immediately!"
}
   
[root@ceph-node5 ~]# awk -f if-else.awk items.txt
Shell More: Give discount on HD Camcorder immediately!
Buy More: Order Refrigerator immediately!
Shell More: Give discount on MP3 Player immediately!
Shell More: Give discount on Tennis Racket immediately!
Buy More: Order Laser Printer immediately!

下面的例子,使用三元操作符,把 items.txt 文件中的每两行都以逗号分隔合并起来。

[root@ceph-node5 ~]#  awk 'ORS=NR%2?",":"
"' items.txt
101,HD Camcorder,Video,210,10,102,Refrigerator,Appliance,850,2
103,MP3 Player,Audio,270,15,104,Tennis Racket,Sports,190,20

 (复杂的就是 awk '{if(NR%2==0) ORS=" "; else ORS=",";print}' items.txt)

 while 循环

 Awk 循环用例执行一系列需要重复执行的动作,只要循环条件为 true,就一直保持循环。 和C 语言类似, awk 支持多种循环结构。

 首先是 while 循环结构.
语法:

while 是 awk 的关键字
condition 是条件表达式
actions 是循环体,如果有多条语句,必须放在{ }中

while首先检查condtion,如果是true,执行actions,执行完后,再次检查condition,如果是true
再次执行
actions,直到 condition false 时,退出循环。
注意,如果第一次执行前
condition 返回 false,那么所有 actions 都不会被执行。
下面的例子中,
BEGIN 区域中的语句会先于其他 awk 语句执行。 While 循环将 50 ’x’追加到
变量
string 中。每次循环都检查变量 count,如果其小于 50,则执行追加操作。因此循环体会
执行
50 次,之后,变量 string 的值被打印出来。

[root@ceph-node5 ~]# awk 'BEGIN { while(count++<50) string=string "x"; print string}'
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

下面的例子计算 items-sold.txt 文件中,每件商品出售的总数。
对于每条记录,需要把从第
2 至第 7 个字段累加起来(第一个字段是编号,因此不用累加)
所以,当循环条件从第
2 个字段(因为 while 之前设置了 i=2),开始,检查是否到达最后一个字
(i<=NF)NF 代表每条记录的字段数。

[root@ceph-node5 ~]# vim while.awk
{
i=2; total=0;
while (i <= NF ){
total = total + $i;
i++;
}
print "Item",$1,":",total,"quantities sold";
}

执行结果:

[root@ceph-node5 ~]#  awk -f while.awk items-sold.txt
Item 101 : 47 quantities sold
Item 102 : 10 quantities sold
Item 103 : 65 quantities sold
Item 104 : 20 quantities sold
Item 105 : 42 quantities sold

do-while 循环

While 循环是一种进入控制判断的循环结构,因为在进入循环体前执行判断。而 do-while
环是一种退出控制循环,在退出循环前执行判断。
do-while 循环至少会执行一次,如果条
件为
true,它将一直执行下去。

语法:

do
action
while(condition)


下面的例子中, print 语句仅会执行一次,因为我们已经确认判断条件为 false。如果这是一
while 结构,在相同的初始化条件下, print 一次都不会执行。

awk 'BEGIN {
count=1;
do
print "This gets printed at least once";
while(count!=1)
}'
执行结果:
This gets printed at least once

下面打印 items-sold.txt 文件中,每种商品的总销售量,输出结果和之前的 while.awk 脚本相
同,不同的是,这次试用 do-while 结构来实现。

[root@ceph-node5 ~]# vim dowhile.awk
{
i=2;
total=0;
do {
total = total + $i;
i++;
}
while(i<=NF)
print "Item",$1,":",total,"quantities sold";
}
[root@ceph-node5 ~]# awk -f dowhile.awk items-sold.txt
Item 101 : 47 quantities sold
Item 102 : 10 quantities sold
Item 103 : 65 quantities sold
Item 104 : 20 quantities sold
Item 105 : 42 quantities sold

for 循环


Awk 的 for 循环和 while 循环一样实用,但语法更简洁易用。
语法:

for(initialization;condition;increment/decrement)


for 循环一开始就执行 initialization,然后检查 condition,如果 condition 为 true,执行 actions,
然后执行 increment 或 decrement.如果 condition 为 true,就会一直重复执行 actions 和increment/decrement。


下面例子打印文件总字段数总和。 I 的初始值为 1,如果 i 小于等于字段数,则当前字段会被追加到总数中,每次循环 i 的值会增加 1.

[root@ceph-node5 ~]# echo "1 2 3 4" | awk '{ for (i=1;i<=NF;i++) total = total + $i } END { print total }'
10


下面的例子,使用 for 循环把文件中的字段反序打印出来。 注意这次在 for 中使用 decrement而不是 increment。
提示:每读入一行数据后, awk 会把 NF 的值设置为当前记录的总字段数。
该例用相反的顺序,从最后一个自动开始到第一个字段,逐个输出每个字段。然后输出换行。

反转示例:

[root@ceph-node5 ~]# vim forreverse.awk
BEGIN {
ORS="";
} {
for (i=NF;i>0;i--)
print $i," "
print "
";
}

[root@ceph-node5 ~]# awk -f forreverse.awk items-sold.txt
12 10 8 5 10 2 101
2 0 3 4 1 0 102
13 5 20 11 6 10 103
5 6 0 4 3 2 104
6 12 7 5 2 10 105



之前我们用 while 和 do-while 循环,输出了 items-sold.txt 中每种商品的销售量,现在我们推
出该程序的 for 循环版本。

[root@ceph-node5 ~]# vim  for.awk
{
total=0;
for(i=2;i<=NF;i++)
total = total + $i
print "Item ",$1," : ",total," quantities sold"
}

[root@ceph-node5 ~]# awk -f for.awk items-sold.txt
Item 101 : 47 quantities sold
Item 102 : 10 quantities sold
Item 103 : 65 quantities sold
Item 104 : 20 quantities sold
Item 105 : 42 quantities sold

break 语句

Break 语句用来跳出它所在的最内层的循环(while,do-while,或 for 循环)。请注意, break 语句
只有在循环中才能使用。
打印某个月销售量为 0 的任何商品,即从第 2 至第 7 个字段中出现 0 的记录。

[root@ceph-node5 ~]# vim break.awk
{
i=2;total=0;
while(i++<=NF)
{
if($i == 0)
{
print "Item",$0,"had a month without item sold"
break
}
}
}

[root@ceph-node5 ~]# awk -f break.awk items-sold.txt
Item 102 0 1 4 3 0 2 had a month without item sold
Item 104 2 3 4 0 6 5 had a month without item sold


如果执行下面的命令,要按 Ctrl+c 才能停止。

[root@ceph-node5 ~]#  awk 'BEGIN{while(1) print "forever"}'

这条语句一直打印字符串”forever”,因为条件永远不为 false。 尽管死循环会用于操作系统和
进程控制,但通常不是什么好事。
下面我们修改这个死循环,让它执行 10 次后退出

awk 'BEGIN{
x=1;
while(1)
{
print "Iteration"
if( x==10)
break;
x++
}}'

其输出结果如下:

Iteration
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration

continue 语句


Continue 语句跳过后面剩余的循环部分,立即进入下次循环。 请注意, continue 只能用在循环当中。
下面打印 items-sold.txt 文件中所有商品的总销售量。其输出结果和 while.awk、 dowhile.awk
以及 for.awk 一样,但是这里的 while 循环中使用 contine,使循环从 1 而不是从 2 开始。

[root@ceph-node5 ~]# vim continue.awk
{
i=1;total=0;
while(i++<=NF)
{
if(i==1)
continue
total = total + $i
}
print "Item",$1,":",total,"quantities sold"
}

[root@ceph-node5 ~]# awk -f continue.awk items-sold.txt
Item 101 : 47 quantities sold
Item 102 : 10 quantities sold
Item 103 : 65 quantities sold
Item 104 : 20 quantities sold
Item 105 : 42 quantities sold

下面的脚本在每次循环时都打印 x 的值,除了第 5 次循环,因为 continue 导致跳打印语句。

awk 'BEGIN{
x=1;
while(x<=10)
{
if(x==5)
{
x++
continue
}
print "Value of x:",x;x++;
}
}'

执行结果:
Value of x: 1
Value of x: 2
Value of x: 3
Value of x: 4
Value of x: 6
Value of x: 7
Value of x: 8
Value of x: 9
Value of x: 10

exit 语句


exit 命令立即停止脚本的运行,并忽略脚本中其余的命令。
exit 命令接受一个数字参数最为 awk 的退出状态码,如果不提供参数,默认的状态码是 0.
下面的脚本执行到第 5 次循环时退出,因为 print 命令位于 exit 之后,所以输出的值只到 4
为止,到第 5 次循环时就退出了。

awk 'BEGIN{
x=1;
while(x<=10)
{
if(x==5)
{
x++
exit
}
print "Value of x:",x;x++;
}
}'


其输出结果如下:

Value of x: 1
Value of x: 2
Value of x: 3
Value of x: 4


下面例子打印第一次出现的有个月没有卖出一件的商品的信息。和 break.awk 脚本很相似,
区别在于,遇到某月为出售的商品时,退出脚本,而不是继续执行。

[root@ceph-node5 ~]# vim exit.awk
{
i=2;total=0;
while(i++<=NF)
if($i==0) {
print "Item",$1,"had a month with no item sold"
exit
}
}

[root@ceph-node5 ~]# awk -f exit.awk items-sold.txt
Item 102 had a month with no item sold




提示: 104 号商品有的月份也没有卖出一件,但是并没有被打印,因为我们在循环中使用了exit 命令。

awk 关联数组

相比较与其他编程语言中的传统数组, awk 的数组更为强大。
Awk 的数组,都是关联数组,即一个数组包含多个”索引/值”的元素。 索引没必要是一系列
连续的数字,实际上,它可以使字符串或者数字,并且不需要指定数组长度。相比较与其他编程语言中的传统数组, awk 的数组更为强大。
Awk 的数组,都是关联数组,即一个数组包含多个”索引/值”的元素。 索引没必要是一系列
连续的数字,实际上,它可以使字符串或者数字,并且不需要指定数组长度。

语法:

arrayname[string]=value 

其中:
arrayname 是数组名称
string 是数组索引 value 是为数组元素赋的值

访问 awk 数组的元素


如果要访问数组中的某个特定元素,使用
arrayname[index] 即可返回该索引中的值。
一个简单的数组赋值示例
:

BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";
item[103]="MP3 Player";
item[104]="Tennis Racket";
item[105]="Laser Printer";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["na"]="Not Available";
print item["101"];
print item[102];
print item["103"];
print item[104];
print item["105"];
print item[1001];
print item["na"];
}

[root@ceph-node5 ~]# awk -f array-assign.awk
HD Camcorder
Refrigerator
MP3 Player
Tennis Racket
Laser Printer
Tennis Ball
Not Available

请注意:

数组索引没有顺序,甚至没有从 01 开始,而是直接从 101….105 开始,然后直接跳到 1001,又降到 55,还有一个字符串索引”na”.
数组索引可以是字符串,数组的最后一个元素就是字符串索引,即”na”
Awk 中在使用数组前,不需要初始化甚至定义数组,也不需要指定数组的长度。
Awk 数组的命名规范和 awk 变量命名规范相同。

awk 的角度来说,数组的索引通常是字符串,即是你使用数组作为索引, awk 也会当做字符串来处理。下面的写法是等价的:

Item[101]="HD Camcorder"
Item["101"]="HD Camcorder"

引用数组元素


如下所示,可以使用 print 命令直接打印数组的元素,也可以把元素的值赋给其他变量以便后续使用。

print item[101]
x=item[105]


如果视图访问一个不存在的数组元素, awk 会自动以访问时指定的索引建立该元素,并赋予null 值。 为了避免这种情况,在使用前最后检测元素是否存在。
使用 if 语句可以检测元素是否存在,如果返回 true,说明改元素存在于数组中。

if ( index in array-name )


一个简单的引用数组元素的例子:

[root@ceph-node5 ~]# vim array-refer.awk
BEGIN {
x = item[55];
if ( 55 in item )
print "Array index 55 contains",item[55];
item[101]="HD Camcorder";
if ( 101 in item )
print "Array index 101 contains",item[101];
if ( 1010 in item )
print "Array index 1010 contains",item[1010];
}

[root@ceph-node5 ~]# awk -f array-refer.awk
Array index 55 contains
Array index 101 contains HD Camcorder



该例中:

Item[55] 在引用前没有赋任何值,所以在引用是 awk 自动创建该元素并赋 null 值
Item[101]是一个已赋值的缘分,所以在检查索引值时返回 true,打印该元素
Item[1010]不存在,因此检查索引值时,返回 false,不会被打印

使用循环遍历 awk 数组


如果要访问数组中的所有元素, 可以使用 for 的一个特殊用法来遍历数组的所有索引:

语法:

for ( var in arrayname )


actions
其中:

var 是变量名称
in 是关键字
arrayname 是数组名
actions 是一系列要执行的 awk 语句,如果有多条语句,必须包含在{ }中。 通过把索引值赋给变量 var,循环体可以把所有语句应用到数组中所有的元素上。


在示例”for (x in item)”中, x 是变量名,用来存放数组索引。
请注意,我们并没有指定循环执行的条件, 实际上我们不比关系数组中有多少个元素,因为
awk 会自动判断,在循环结束前遍历所有元素。
下面的例子遍历数组中所有元素并打印出来。

[root@ceph-node5 ~]# vim array-for-loop.awk
BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";
item[103]="MP3 Player";
item[104]="Tennis Racket";
item[105]="Laser Printer";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["no"]="Not Available";
for(x in item)
print item[x]
}

[root@ceph-node5 ~]# awk -f array-for-loop.awk
Not Available
Laptop
HD Camcorder
Refrigerator
MP3 Player
Tennis Racket
Laser Printer
Tennis Ball


删除数组元素


如果要删除特定的数组元素,使用 delete 语句。一旦删除了某个元素,就再也获取不到它的值了

语法:

delete arrayname[index];


删除数组内所有元素

for (var in array)
delete array[var]


在 GAWK 中,可以使用单个 delete 命令来删除数组的所有元素:

Delete array


此外,下面例子中,item[103]=""并没有删除整个元素,仅仅是给它赋了 null 值。

[root@ceph-node5 ~]# vim array-delete.awk
BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";
item[103]="MP3 Player";
item[104]="Tennis Racket";
item[105]="Laser Printer";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["no"]="Not Available";
delete item[102]
item[103]=""
delete item[104]
delete item[1001]
delete item["na"]
for(x in item)
print "Index",x,"contains",item[x]
}
[root@ceph-node5 ~]# awk -f array-delete.awk
Index no contains Not Available
Index 55 contains Laptop
Index 101 contains HD Camcorder
Index 103 contains
Index 105 contains Laser Printer

多维数组


虽然 awk 只支持一维数组,但其奇妙之处在于,可以使用一维数组来模拟多维数组。
假定要创建下面的 2X2 维数组:

10 20
30 40


其中位于"1,1"的元素是 10,位于”1,2”的元素是 20,等等…,下面把 10 赋值给”1,1”的元素:

item["1,1"]=10


即使使用了"1,1"作为索引值,它也不是两个索引,仍然是单个字符串索引,值为"1,1"。所以上面的写法中,实际上是把 10 赋给一维数组中索引"1,1"代表的值。

[root@ceph-node5 ~]# vim array-multi.awk
BEGIN {
item["1,1"]=10;
item["1,2"]=20;
item["2,1"]=30;
item["2,2"]=40
for (x in item)
print item[x]
}

[root@ceph-node5 ~]# awk -f array-multi.awk
10
20
30
40


现在把索引外面的引号去掉,会发生什么情况? 即 item[1,1](而不是 item["1,1"]):

[root@ceph-node5 ~]# vim array-multi2.awk
BEGIN {
item[1,1]=10;
item[1,2]=20;
item[2,1]=30;
item[2,2]=40
for (x in item)
print item[x]
}

[root@ceph-node5 ~]# awk -f array-multi2.awk
30
40
10
20



上面的例子仍然可以运行,但是结果有所不同。在多维数组中,如果没有把下标用引号引住,awk 会使用"34"作为下标分隔符。
当指定元素 item[1,2]时,它会被转换为 item[“1342”]。 Awk 用把两个下标用”34”连接起来并转换为字符串。

 当使用["1,2"]时,则不会使用"34",它会被当做一维数组。
如下示例:

[root@ceph-node5 ~]# vim array-multi3.awk
BEGIN {
item["1,1"]=10;
item["1,2"]=20;
item[2,1]=30;
item[2,2]=40;
for(x in item)
print "Index",x,"contains",item[x];
}

[root@ceph-node5 ~]# awk -f array-multi3.awk
Index 1,1 contains 10
Index 1,2 contains 20
Index 21 contains 30
Index 22 contains 40




其中:

索引"1,1"和"1,2"放在了引号中,所以被当做一维数组索引, awk 没有使用下标分隔符,因此,索引值被原封不动地输出。
所以 2,12,2 没有放在引号中,所以被当做多维数组索引, awk 使用下标分隔符来处理,因此索引变成"20341"和"20342",于是在两个下标直接输出了非打印字符"034"

SUBSEP 下标分隔符


通过变量 SUBSEP 可以把默认的下标分隔符改成任意字符,下面例子中, SUBSEP 被改成了分号:

[root@ceph-node5 ~]# vim array-multi4.awk
BEGIN {
SUBSEP=":";
item["1,1"]=10;
item["1,2"]=20;
item[2,1]=30;
item[2,2]=40;
for(x in item)
print "Index",x,"contains",item[x];
}

[root@ceph-node5 ~]# awk -f array-multi4.awk
Index 1,1 contains 10
Index 1,2 contains 20
Index 2:1 contains 30
Index 2:2 contains 40

这个例子中,索引"1,1"和"1,2"由于放在了引号中而没有使用 SUBSEP 变量。



所以,使用多维数组时,最好不要给索引值加引号,如:

[root@ceph-node5 ~]# vim  array-multi5.awk
BEGIN {
SUBSEP=":";
item[1,1]=10;
item[1,2]=20;
item[2,1]=30;
item[2,2]=40;
for(x in item)
print "Index",x,"contains",item[x];
}

[root@ceph-node5 ~]# awk -f array-multi5.awk
Index 1:1 contains 10
Index 1:2 contains 20
Index 2:1 contains 30
Index 2:2 contains 40


 asort 为数组排序


asort 函数重新为元素值排序,并且把索引重置为从 1 n 的值,此处 n 代表数组元素个数。
假定一个数组有两个元素
:item["something"]="B - I'm big b"item["notsure"]="A – I'm big a"调用 asort 函数,数组会以元素值排序,

变成:item[1]="A – I'm big a"item[2]="B – I'm bigb"
下面例子中,数组索引是非连续的数字和字符串,调用 asort 后,元素值被排序,并且索引值变成 1,2,3,4…. 请注意, asort 函数会返回数组元素的个数。

[root@ceph-node5 ~]# vim asort.awk
BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";

item[103]="MP3 Player";
item[104]="Tennis Racket";
item[105]="Laser Printer";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["na"]="Not Available";
print "---------- Before asort -------------"
for(x in item)
print "Index",x,"contains",item[x]
total = asort(item);
print "---------- After asort -------------"
for(x in item)
print "Index",x,"contains",item[x]
print "Return value from asort:",total;
}

[root@ceph-node5 ~]# awk -f asort.awk
---------- Before asort -------------
Index 55 contains Laptop
Index 101 contains HD Camcorder
Index 102 contains Refrigerator
Index 103 contains MP3 Player
Index 104 contains Tennis Racket
Index 105 contains Laser Printer
Index na contains Not Available
Index 1001 contains Tennis Ball
---------- After asort -------------
Index 4 contains MP3 Player
Index 5 contains Not Available
Index 6 contains Refrigerator
Index 7 contains Tennis Ball
Index 8 contains Tennis Racket
Index 1 contains HD Camcorder
Index 2 contains Laptop
Index 3 contains Laser Printer
Return value from asort: 8

这个例子中, asort 之后,数组打印顺序不是按索引值从 1 到 8,而是随机的。可以用下面
的方法,按索引值顺序打印:

[root@ceph-node5 ~]# vim asort1.awk
BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";
item[103]="MP3 Player";
item[104]="Tennis Racket";
item[105]="Laser Printer";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["na"]="Not Available";
total = asort(item);
for(i=1;i<=total;i++)
print "Index",i,"contains",item[i]
}

[root@ceph-node5 ~]# awk -f asort1.awk
Index 1 contains HD Camcorder
Index 2 contains Laptop
Index 3 contains Laser Printer
Index 4 contains MP3 Player
Index 5 contains Not Available
Index 6 contains Refrigerator
Index 7 contains Tennis Ball
Index 8 contains Tennis Racket

或许你已经注意到,一旦调用 asort 函数,数组原始的索引值就不复存在了。 因此,你可能
想在不改变原有数组索引的情况下,使用新的索引值创建一个新的数组。
下面的例子中,原始数组
item不会被修改,相反,使用排序后的新索引值创建新数组itemnew,
itemnew[1],itemnew[2],itemnew[3],等等。

total = asort(item,itemnew);


再次申明,务必牢记 asort 函数按元素值排序,但排序后使用从 1 开始的新索引值,原先的索引被覆盖掉了

用 asorti 为索引排序
和以元素值排序相似,也可以取出所有索引值,排序,然后把他们保存在新数组中。
下面的例子展示了 asort 和 asorti 的不同,请牢记下面两点:

asorti 函数为索引值(不是元素值)排序,并且把排序后的元素值当做元素值保存。
如果使用 asorti(state)将会丢失原始元素值,即索引值变成了元素值。因此为了保险起见,通常给 asorti 传递两个参数,即 asorti(state,statebbr).
这样一来,原始数组state 就不会被覆盖了。

如下:

[root@ceph-node5 ~]# vim asorti.awk
BEGIN {
state["TX"]="Texas";
state["PA"]="Pennsylvania";
state["NV"]="Nevada";
state["CA"]="California";
state["AL"]="Alabama";
print "-------------- Function: asort -----------------"
total = asort(state,statedesc);
for(i=1;i<=total;i++)
print "Index",i,"contains",statedesc[i];
print "-------------- Function: asorti -----------------"
total = asorti(state,stateabbr);
for(i=1;i<=total;i++)
print "Index",i,"contains",stateabbr[i];
}

[root@ceph-node5 ~]# awk -f asorti.awk
-------------- Function: asort -----------------
Index 1 contains Alabama
Index 2 contains California
Index 3 contains Nevada
Index 4 contains Pennsylvania
Index 5 contains Texas
-------------- Function: asorti -----------------
Index 1 contains AL
Index 2 contains CA
Index 3 contains NV
Index 4 contains PA
Index 5 contains TX

其他 awk 命令


使用 printf 格式化输出


printf 可以非常灵活、简单地以你期望的格式输出结果。
语法:

printf "print format", variable1,variable2,etc

printf 中的特殊字符


printf 中可以使用下面的特殊字符

使用换行符把 Line1 和 Line2 打印在单独的行里:

[root@ceph-node5 ~]# awk 'BEGIN {printf "Line 1
Line 2
"}'
Line 1
Line 2



以制表符分隔字段, Field 1 后面有两个制表符:

[root@ceph-node5 ~]# awk 'BEGIN { printf "Field 1		Field 2	Field 3	Field 4
"}'
Field 1         Field 2 Field 3 Field 4



每个字段后面使用垂直制表符:

[root@ceph-node5 ~]# awk 'BEGIN { printf "Field 1vField 2vField 3vField 4
" }'
Field 1
       Field 2
              Field 3
                     Field 4



下面的例子中,除了第 4 个字段外,每个字段后面使用退格符,这会擦除前三个字段最后的数字。

如"Field 1"会被显示为"Field",因为最后一个字符被退格符擦除了。 然而"Field 4"会照旧输出,因为它后面没有使用.

[root@ceph-node5 ~]#  awk 'BEGIN { printf "Field 1Field 2Field 3Field 4
" }'
Field Field Field Field 4



下面的例子,打印每个字段后,执行一个“回车”, 在当前打印的字段的基础上,打印下一个字段。这就意味着, 最后只能看到”Field 4”,因为其他的字段都被覆盖掉了.

[root@ceph-node5 ~]# awk 'BEGIN { printf "Field 1
Field 2
Field 3
Field 4
" }'
Field 4

使用 OFS,ORS

当使用 print(不是 printf)打印多个以逗号分隔的字段时, awk 默认会使用内置变量 OFS 和 ORS处理输出。

[root@ceph-node5 ~]# vim print.awk
BEGIN {
FS=",";
OFS=":";
ORS="
--
";
} {
print $2,$3
}

[root@ceph-node5 ~]# awk -f print.awk items.txt
HD Camcorder:Video
--
Refrigerator:Appliance
--
MP3 Player:Audio
--
Tennis Racket:Sports
--
Laser Printer:Office
--

Printf 不受 OFS,ORS 影响


printf 不会使用 OFS 和 ORS,它只根据"format"里面的格式打印数据,如下所示:

[root@ceph-node5 ~]# vim printf1.awk
BEGIN {
FS=",";
OFS=":";
ORS="
--
";
} {
printf "%s^^%s
",$2,$3
}
 
[root@ceph-node5 ~]# awk -f printf1.awk items.txt
HD Camcorder^^Video
Refrigerator^^Appliance
MP3 Player^^Audio
Tennis Racket^^Sports
Laser Printer^^Office

printf 格式化字符

 

数组作业

把当前系统使用频率最高的前10条命令输出

awk  '{ comm[$1]++}END{for( i  in  comm){print  i,comm[i]}}'  /root/.bash_history   |  sort  -rnk   2   |   head

其中各个命令的含义:

comm[$1]++} :$1是文件中的第一列  ++ 当第一列数据相同时自加1
END{for( i  in  comm)  
{print  i,comm[i]}}' 
 /root/.bash_history  
|  sort  -rnk   2   
|   head

2 、输出当前系统1小时内占用CPU最多的前10个进程

#!/bin/bash
for((i=0;i<60;i++))
do
    ps   -eo   comm,pcpu  |  tail   -n   +2   >>  /tmp/top10.txt
    sleep  60
done
awk '{ process[$1]+=$2}END{for(  j   in  process){ print  i,process[i]}}'  /tmp/top10.txt   |  sort  -rnk    2  | head

awk练习题:

1、只显示uid是501的用户的详细信息
awk -F ":" '$3=="501"{print $0}' /etc/passwd



2、输出文件中偶数行的内容
awk 'FNR%2!=1{print $0}' a.txt 


3、统计系统内有多少个用户不能登录系统
 awk -F ":" '$7~"no"{print $0;i++}END{print i}' /etc/passwd

41-100间 是7的倍数或是含7的数显示出来
awk 'FNR%7==0{print $0}{print FNR}' /etc/passwd



5、统计当前系统有多少个内建帐户


6、统计当前系统有多少个自定义帐户
awk -F ":" 'BEGIN{i=0;j=0}$3<500{i++}$3>=500{j++}END{print "uid 小于 500的用户有 "i"个";print "uid 大于等于 500的用户有"j"个"}'  /etc/passwd

7、把用户名含数字的用户信息输出
awk -F ":" '$1~/[0-9]/{print $0}' aa.txt

8、显示前5个系统用户的用户名、uid、shell

IPsec 密钥交换 IKE

原文地址:https://www.cnblogs.com/zhongguiyao/p/8996074.html