awk是处理文本文件的一个应用程序,它依次处理文件的每一行,并读取里面的每一个字段。对于日志、CSV 那样的每行格式相同的文本文件,awk可能是最方便的工具。

awk命令作用:

  1. 文本处理
  2. 输出格式化的文本报表
  3. 执行算数运算
  4. 执行字符串操作
  5. 运行系统命令
  6. 等等

基本用法

awk的基本用法就是下面的形式。

# 格式
$ awk 动作 文件名

# 示例
$ awk '{print $0}' demo.txt

上面示例中,demo.txt是awk所要处理的文本文件。前面单引号内部有一个大括号,里面就是每一行的处理动作print $0。其中,print是打印命令,$0代表当前行,因此上面命令的执行结果,就是把每一行原样打印出来。

下面,我们先用标准输入(stdin)演示上面这个例子。

$ echo 'this is a test' | awk '{print $0}'
this is a test

上面代码中,print $0就是把标准输入this is a test,重新打印了一遍。

awk会根据空格和制表符,将每一行分成若干字段,依次用$1、$2、$3代表第一个字段、第二个字段、第三个字段等等。

$ echo 'this is a test' | awk '{print $3}'
a

上面代码中,$3代表this is a test的第三个字段a。

下面,为了便于举例,我们把/etc/passwd文件保存成demo.txt。

root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync

这个文件的字段分隔符是冒号(:),所以要用-F参数指定分隔符为冒号。然后,才能提取到它的第一个字段。

$ awk -F ':' '{ print $1 }' demo.txt
root
daemon
bin
sys
sync

变量

除了$ + 数字表示某个字段,awk还提供其他一些变量。

变量NF表示当前行有多少个字段,因此$NF就代表最后一个字段。

$ echo 'this is a test' | awk '{print $NF}'
test

$(NF-1)代表倒数第二个字段。

$ awk -F ':' '{print $1, $(NF-1)}' demo.txt
root /root
daemon /usr/sbin
bin /bin
sys /dev
sync /bin

上面代码中,print命令里面的逗号,表示输出的时候,两个部分之间使用空格分隔。

变量NR表示当前处理的是第几行。

$ awk -F ':' '{print NR ") " $1}' demo.txt
1) root
2) daemon
3) bin
4) sys
5) sync

上面代码中,print命令里面,如果原样输出字符,要放在双引号里面。

awk的其他内置变量如下。

FILENAME:当前文件名
FS:字段分隔符,默认是空格和制表符。
RS:行分隔符,用于分割每一行,默认是换行符。
OFS:输出字段的分隔符,用于打印时分隔字段,默认为空格。
ORS:输出记录的分隔符,用于打印时分隔记录,默认为换行符。
OFMT:数字输出的格式,默认为%.6g。

函数

awk还提供了一些内置函数,方便对原始数据的处理。

函数toupper()用于将字符转为大写。

$ awk -F ':' '{ print toupper($1) }' demo.txt
ROOT
DAEMON
BIN
SYS
SYNC

上面代码中,第一个字段输出时都变成了大写。

常用函数如下

tolower():字符转为小写。
length():返回字符串长度。
substr():返回子字符串。
sin():正弦。
cos():余弦。
sqrt():平方根。
rand():随机数。
awk内置函数的完整列表,可以查看手册。

条件

awk允许指定输出条件,只输出符合条件的行。

输出条件要写在动作的前面。

$ awk '条件 动作' 文件名

请看下面的例子。

$ awk -F ':' '/usr/ {print $1}' demo.txt
root
daemon
bin
sys

上面代码中,print命令前面是一个正则表达式,只输出包含usr的行。

下面的例子只输出奇数行,以及输出第三行以后的行。

# 输出奇数行
$ awk -F ':' 'NR % 2 == 1 {print $1}' demo.txt
root
bin
sync

# 输出第三行以后的行
$ awk -F ':' 'NR >3 {print $1}' demo.txt
sys
sync

下面的例子输出第一个字段等于指定值的行。

$ awk -F ':' '$1 == "root" {print $1}' demo.txt
root

$ awk -F ':' '$1 == "root" || $1 == "bin" {print $1}' demo.txt
root
bin

if 语句

awk提供了if结构,用于编写复杂的条件。

$ awk -F ':' '{if ($1 > "m") print $1}' demo.txt
root
sys
sync

上面代码输出第一个字段的第一个字符大于m的行。

if结构还可以指定else部分。

$ awk -F ':' '{if ($1 > "m") print $1; else print "---"}' demo.txt
root
---
---
sys
sync

AWK的程序结构:

BEGIN 语句块

BEGIN语句块的语法

BEGIN {awk-commands}
BEGIN语句块在程序开始的使用执行,它只执行一次,在这里可以初始化变量。BEGIN是AWK的关键字,因此它必须为大写,注意,这个语句块是可选的。

BODY 语句块

BODY语句块的语法

/pattern/ {awk-commands}
BODY语句块中的命令会对输入的每一行执行,我们也可以通过提供模式来控制这种行为。注意,BODY语句块没有关键字。

END 语句块

END语句块的语法

END {awk-commands}
END语句块在程序的最后执行,END是AWK的关键字,因此必须为大写,它也是可选的。

让我们创建一个包含序号,学生姓名,科目名称和得分的文件 marks.txt。

1)  Amit    Physics  80
2)  Rahul   Maths    90
3)  Shyam   Biology  87
4)  Kedar   English  85
5)  Hari    History  89
下面的例子中我们将会显示文件内容,并且添加每一列的标题

$ awk 'BEGIN{printf "Sr NotNametSubtMarksn"} {print}' marks.txt
上述代码执行后,输出以下内容

Sr No     Name     Sub          Marks
 1)       Amit     Physics      80
 2)       Rahul    Maths        90
 3)       Shyam    Biology      87
 4)       Kedar    English      85
 5)       Hari     History      89
在程序的开始,AWK在BEGIN语句中打印出标题。然后再BODY语句中,它会读取文件的每一行然后执行AWK的print命令将每一行的内容打印到标准输出。这个过程会一直重复直到文件的结尾。

awk调用方式:

1.命令行方式
awk [-F  field-separator]  'commands'  input-file(s)
其中,commands 是真正awk命令,[-F域分隔符]是可选的。 input-file(s) 是待处理的文件。
在awk中,文件的每一行中,由域分隔符分开的每一项称为一个域。通常,在不指名-F域分隔符的情况下,默认的域分隔符是空格。

2.shell脚本方式
将所有的awk命令插入一个文件,并使awk程序可执行,然后awk命令解释器作为脚本的首行,一遍通过键入脚本名称来调用。
相当于shell脚本首行的:#!/bin/sh
可以换成:#!/bin/awk

3.将所有的awk命令插入一个单独文件,然后调用:
awk -f awk-script-file input-file(s)
其中,-f选项加载awk-script-file中的awk脚本,input-file(s)跟上面的是一样的。

命令实例:

#输出marks.txt的完整内容
$ awk '{print}' marks.txt 
$ awk '{print $0}' marks.txt

#输出marks.txt每一行以空格为间隔的第一块内容,(默认以空格为间隔)
$ awk '{print $1}' marks.txt
#输出第二块内容
$ awk '{print $2}' marks.txt
$ awk '{print $3 "t" $4}' marks.txt

#输出marks.txt文件中字符数大于18的行
$ awk 'length($0) > 18' marks.txt
$ awk 'length($0) > 18 {print $0}' marks.txt

#输出/etc/passwd文件中每行以:间隔的第一个域
$ cat /etc/passwd |awk  -F ':'  '{print $1}'  

#输出access_stat4.log文件中含有content的行到gl.log文件中 >> 表示追加,> 表示覆盖
$ /bin/awk '/content/'  access_stat4.log >> ./gl/gl.log

$ /bin/awk '/content/ {print $1}'  access_stat4.log >> ./gl/gl.log

#输出test.log文件中含有content的行数
$ awk '/content/{++cnt} END {print "Count = ", cnt}' test.log
#输出test.log文件中含有content的行及其行数
$ awk '/content/{++cnt;print} END {print "Count = ", cnt}' test.log

#自定义间隔符,以+号为间隔符输出test.log文件每行的第一块
$ awk -F "+" '{print $1}' test.log
#以+或8为间隔符
$ awk -F "+|8" '{print $1}' test.log

#正则  输出test.log文件中不含有如下字符的行  正则表达式操作符使用 ~ 和 !~ 分别代表匹配和不匹配。
$ awk '{if($0 !~ /Googlebot|Baiduspider|Sogou|bingbot|Yahoo/) print $0}' test.log
#输出test.log文件中以空格为间隔第一块不含有如下字符的行
$ awk '{if($1 !~ /Googlebot|Baiduspider|Sogou|bingbot|Yahoo/) print $0}' test.log


#正则  输出test.log文件中含有 content/数字  的行
$ awk '{if($0 ~ /content/[0-9]+/) print $0}' test.log

#排序  对nginx_access.log文件中第一块出现行数数量进行排序
$ awk '{print $1}' nginx_access.log |sort |uniq -c|sort -n #升序
$ awk '{print $1}' nginx_access.log |sort |uniq -c|sort -nr #降序

#统计某个文件夹下的所有文件占用的字节数
$ ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size}'
#统计某个文件夹下的每个文件占用的字节数
$ ls -l |awk '{size=0;} {size=size+$5;} {print "[end]size is ", size}'


#条件语句
$ ls -l |awk 'BEGIN {size=0;print "[start]size is ", size} {if($5!=4096){size=size+$5;}} END{print "[end]size is ", size/1024/1024,"M"}' 

#循环与数组
$ awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;}; END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd

awk内置变量

awk有许多内置变量用来设置环境信息,这些变量可以被改变,下面给出了最常用的一些变量。

ARGC               命令行参数个数
ARGV               命令行参数排列
ENVIRON            支持队列中系统环境变量的使用
FILENAME           awk浏览的文件名
FNR                浏览文件的记录数
FS                 设置输入域分隔符,等价于命令行 -F选项
NF                 浏览记录的域的个数
NR                 已读的记录数
OFS                输出域分隔符
ORS                输出记录分隔符
RS                 控制记录分隔符
#此外,$0变量是指整条记录。$1表示当前行的第一个域,$2表示当前行的第二个域,......以此类推。

#统计/etc/passwd:文件名,每行的行号,每行的列数,对应的完整行内容:

$ awk  -F ':'  '{print "filename:" FILENAME ",linenumber:" NR ",columns:" NF ",linecontent:"$0}' /etc/passwd

输出:

filename:/etc/passwd,linenumber:1,columns:7,linecontent:root:x:0:0:root:/root:/bin/bash
filename:/etc/passwd,linenumber:2,columns:7,linecontent:daemon:x:1:1:daemon:/usr/sbin:/bin/sh
filename:/etc/passwd,linenumber:3,columns:7,linecontent:bin:x:2:2:bin:/bin:/bin/sh
filename:/etc/passwd,linenumber:4,columns:7,linecontent:sys:x:3:3:sys:/dev:/bin/sh
#使用printf替代print,可以让代码更加简洁,易读

$ awk  -F ':'  '{printf("filename:%10s,linenumber:%s,columns:%s,linecontent:%sn",FILENAME,NR,NF,$0)}' /etc/passwd

while循环:

$ awk 'BEGIN{ test=100; total=0; while(i<=test) { total+=i; i++; } print total; }'

for循环:

$ awk 'BEGIN{  total=0;  for(i=0;i<=100;i++)  {  total+=i;  }  print total;  }'  

$ awk 'BEGIN{  for(k in ENVIRON)  {  print k"="ENVIRON[k];  }  }'

do 循环:

$ awk 'BEGIN{ total=0; i=0; do {  total+=i; i++; }while(i<=100) print total; }'

awk脚本命令:

!/bin/awk -f   
# all comment lines must start with a hash '#'  
# name: student_tot.awk  
# to call: student_tot.awk grade.txt  
# prints total and average of club student points  
# print a header first  
BEGIN  
 {  
   print "Student    Date   Member No.  Grade  Age  Points  Max"  
   print "Name  Joined Gained  Point Available"  
   print"========================================================="  
 }  

# let's add the scores of points gained  
(tot+=$6);  

# finished processing now let's print the total and average point  
END  
{  
    print "Club student total points :" tot  
    print "Average Club Student points :" tot/N  
}
#输出nginx日志中每秒访问数超过40的时间点
/bin/awk -F '[][]' '{print $2}' /var/log/access.log |sort |uniq -c|sort -n | /bin/awk '$1 >= 40{print $0}'

#nginx日志访问ip量排序
awk '{print $1}' nginx_access.log |sort |uniq -c|sort -n
条件表达式
==   !=   >   >=  
awk -F":" '$1=="mysql"{print $3}' /etc/passwd  
awk -F":" '{if($1=="mysql") print $3}' /etc/passwd          //与上面相同 
awk -F":" '$1!="mysql"{print $3}' /etc/passwd                 //不等于
awk -F":" '$3>1000{print $3}' /etc/passwd                      //大于
awk -F":" '$3>=100{print $3}' /etc/passwd                     //大于等于
awk -F":" '$3<1{print $3}' /etc/passwd                            //小于
awk -F":" '$3<=1{print $3}' /etc/passwd                         //小于等于
#多分隔符
awk -F'[:#]' '{print NF}'  helloworld.sh           #指定多个分隔符: #,输出每行多少字段
awk -F'[:#]' '{print $1,$2,$3,$4,$5,$6}' OFS='t' helloworld.sh #制表符分隔输出多字段

awk -F'[:#/]' '{print NF}' helloworld.sh         #指定三个分隔符,并输出每行字段数
awk -F'[:#/]' '{print $1,$2,$3,$4,$5,$6,$7,$8,$9}' helloworld.sh #制表符分隔输出多字段

awk -F ‘[][]’ ‘{print $3;}’ data   #以[和]为分隔符

每行都是数字的文件中,求平均值

gzcat  default.log.gz | awk -F 'speed=|H/s&ssas' '{print $2}' | awk 'BEGIN{sum=0}{sum+=$1}END{print sum/FNR}'