awk是处理文本文件的一个应用程序,它依次处理文件的每一行,并读取里面的每一个字段。对于日志、CSV 那样的每行格式相同的文本文件,awk可能是最方便的工具。
awk命令作用:
文本处理
输出格式化的文本报表
执行算数运算
执行字符串操作
运行系统命令
等等
基本用法
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}'