十五 awk文本处理

Posted 钟桂耀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十五 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\\n-------\\n"}{ 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 "---------\\nName Title\\n------------\\n";}{print $2,"\\t",$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 "---------------------------\\nName\\tTitle\\n------------------------"}\\
{print $2,"\\t",$3;}\\
END {print "-----------------------------------------"}\' employee.txt 

结果:

[root@ceph-node5 ~]# awk \'BEGIN { FS=",";
> print "---------------------------\\nName\\tTitle\\n------------------------"}\\
> {print $2,"\\t",$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,其输出变量之间没有任何空格。

以上是关于十五 awk文本处理的主要内容,如果未能解决你的问题,请参考以下文章

Linux文本处理三剑客——awk详细用法

awk

文本处理工具awk

linux awk命令的使用

awk文本处理

文本处理工具awk常用命令