Linux Shell 020-文本行处理工具awk

2024-01-01 10:36:39

Linux Shell 020-文本行处理工具awk

本节关键字:Linux、Bash Shell、文本处理
相关指令:awk

awk介绍

  • awk是一种编程语言,主要用于在linux/unix下对文本和数据进行处理,是linux/unix下的一个工具。数据可以来自标准输入、一个或多个文件,或其它命令的输出。
  • awk的处理文本和数据的方式:逐行扫描文件,默认从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。
  • awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。
  • gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。
  • awk是以GNU的gawk为例的,在linux系统中已把awk链接到gawk,所以下面全部以awk进行介绍。

awk语法

命令行模式

基本语法:awk 选项 ‘命令部分’ 文件名
补充说明:引用shell变量需用双引号引起

命令部分说明

# 1、正则表达式,地址定位
'/root/{awk语句}'            # sed中: '/root/p'
'NR==1,NR==5{awk语句}'       # sed中: '1,5p'
'/^root/,/^ftp/{awk语句}'    # sed中:'/^root/,/^ftp/p'

# 2、{awk语句1**;awk语句2;**...}
'{print $0;print $1}'       # sed中:'p'
'NR==5{print $0}'           # sed中:'5p'
# 注:awk命令语句间用分号间隔

# 3、BEGIN...END....
'BEGIN{awk语句};{处理中};END{awk语句}'
'BEGIN{awk语句};{处理中}'
'{处理中};END{awk语句}'
脚本模式

脚本编写

# 定义魔法字符
#!/bin/awk -f
# 以下是awk引号里的命令清单,不要用引号保护命令,多个命令用分号间隔
BEGIN{FS=":"}
NR==1,NR==3{print $1"\t"$NF}
...

脚本执行

# 方法1:
# awk 选项 -f awk的脚本文件  要处理的文本文件
awk -f awk.sh filename
sed -f sed.sh -i filename

# 方法2:
# ./awk的脚本文件(或者绝对路径)    要处理的文本文件
./awk.sh filename
./sed.sh filename

awk选项

选项说明
-F定义字段分割符号,默认的分隔符是空格
-v定义变量并赋值

awk内部变量

变量变量说明备注
$0当前处理行的所有记录
$1,$2,$3…$n文件中每行以间隔符号分割的不同字段awk -F: ‘{print $1,$3}’
NF当前记录的字段数(列数)awk -F: ‘{print NF}’
$NF最后一列$(NF-1)表示倒数第二列
FNR/NR行号
FS定义间隔符‘BEGIN{FS=“:”};{print $1,$3}’
OFS定义输出字段分隔符,默认空格‘BEGIN{OFS=“\t”};print $1,$3}’
RS输入记录分割符,默认换行‘BEGIN{RS=“\t”};{print $0}’
ORS输出记录分割符,默认换行‘BEGIN{ORS=“\n\n”};{print $1,$3}’
FILENAME当前输入的文件名

awk常用内置变量举例

# 打印1.txt中的所有行
$ awk '{print $0}' 1.txt

# 打印1.txt的1到5行
$ awk 'NR==1,NR==5{print $0}' 1.txt        

# 打印第1行或者第5行
$ awk 'NR==1 || NR==5{print $0}' 1.txt        

# 打印第3行到第5行
$ awk 'NR>=3 && NR<=5{print $0}' 1.txt        

# 打印3到最后一行
$ awk 'NR>=3,NR<=5{print $0}' 1.txt        

# 打印以:为分隔符的第一列,倒数第二列和最后一列
$ awk -F: '{print $1,$NF,$(NF-1)}' 1.txt        

# 打印以,为分隔符包含DI的行的第4列和第10列到2.txt
$ awk -F, '/DI/{print $4,$10}' 1.txt > 2.txt     

# 打印以:为分隔符的最后一列
$ awk -F: '{print NF}' 1.txt            

# 打印1到5行
$ awk 'NR==1,NR==5' 1.txt                

# 打印包含root的行
$ awk '/root/{print $0}' 1.txt            
$ awk '/root/' 1.txt                

# 以:分隔,打印包含root行的第一列和最后一列
$ awk -F: '/root/{print $1,$NF}' 1.txt        

# 以:分隔,打印包含root的行
$ awk -F: '/root/{print $0}' 1.txt        

# 打印1到5行,打印以root开头的行
$ awk 'NR==1,NR==5;/^root/{print $0}' 1.txt    
$ awk 'BEGIN{FS=":"};/^root/,/^lp/{print $1,$NF}' 1.txt
$ awk -F: 'BEGIN{OFS="\t\t"};/^root/,/^lp/{print $1,$NF}' 1.txt

# 输入间隔符为冒号,输出分隔符为@@@,以root开头和以lp开头的行,打印第1列和最后一列
$ awk -F: 'BEGIN{OFS="@@@"};/^root/,/^lp/{print $1,$NF}' 1.txt    

# 输入间隔符为: 输出间隔符为******
$ awk 'BEGIN{FS=":"};{print $1"*****"$NF}' 1.txt    

# 输入行结尾符为\t(默认为换行),输出行结尾符为\n(换行)
$ awk -F: 'BEGIN{RS="\t"};{print $0}' 1.txt    

# 输入行结尾符为\t(默认为换行),输出行结尾符为\n(换行)
$ awk -F: 'BEGIN{RS="\t",ORS="\n"};{printf $1}' 1.txt   

# 以,分割,打印包含AI的行并格式化到2.txt 
$ awk -F, '/AI/{print "update set value=123.45 from " $10 " where name='\''"$4"'\''"}' 1.txt > 2.txt   


awk常用内置变量分隔符举例

FS和OFS

$ awk 'BEGIN{FS=":"};/^root/,/^lp/{print $1,$NF}' 1.txt
$ awk -F: 'BEGIN{OFS="\t\t"};/^root/,/^lp/{print $1,$NF}' 1.txt        
$ awk -F: 'BEGIN{OFS="@@@"};/^root/,/^lp/{print $1,$NF}' 1.txt     

RS和ORS

# 修改源文件前2行增加制表符和内容:
$ vim 1.txt
root:x:0:0:root:/root:/bin/bash hello   world
bin:x:1:1:bin:/bin:/sbin/nologin        test1   test2

$ awk 'BEGIN{RS="\t"};{print $0}' 1.txt
$ awk 'BEGIN{ORS="\t"};{print $0}' 1.txt

awk工作原理

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

1、awk使用一行作为输入,并将这一行赋给内部变量$0,每一行也可称为一个记录,以换行符(RS)结束
2、每行被间隔符:(默认为空格或制表符)分解成字段(或域),每个字段存储在已编号的变量中,从$1开始
问:awk如何知道用空格来分隔字段的呢?
答:因为有一个内部变量FS来确定字段分隔符。初始时,FS赋为空格
3、awk使用print函数打印字段,打印出来的字段会以空格分隔,因为$1,$3之间有一个逗号。逗号比较特殊,它映射为另一个内部变量,称为输出字段分隔符OFS,OFS默认为空格
4、awk处理完一行后,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕

awk格式化输出print和printf

# print函数,类似echo "hello world"

$ date |awk '{print "Month: "$2 "\nYear: "$NF}'
$ awk -F: '{print "username is: " $1 "\t uid is: "$3}' /etc/passwd

# printf函数,类似echo -n
$ awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}'  /etc/passwd
$ awk -F: '{printf "|%15s| %10s| %15s|\n", $1,$2,$3}' /etc/passwd
$ awk -F: '{printf "|%-15s| %-10s| %-15s|\n", $1,$2,$3}' /etc/passwd

awk 'BEGIN{FS=":"};{printf "%-15s %-15s %-15s\n",$1,$6,$NF}' a.txt

# %s 字符类型  strings %-20s
# %d 数值类型
# 占15字符
# - 表示左对齐,默认是右对齐
# printf默认不会在行尾自动换行,加\n

awk变量定义

$ awk -v NUM=3 -F: '{ print $NUM }' /etc/passwd
$ awk -v NUM=3 -F: '{ print NUM }' /etc/passwd
$ awk -v num=1 'BEGIN{print num}' 
$ awk -v num=1 'BEGIN{print $num}' 

注意:

  • awk中调用定义的变量不需要加$

awk中BEGIN…END的使用

BEGIN:表示在程序开始前执行
?END :表示所有文件处理完后执行
用法:‘BEGIN{开始处理之前};{处理中};END{处理结束后}’

使用示例

$ awk -F: 'BEGIN{ print "Login_shell\t\tLogin_home\n*******************"};{print $NF"\t\t"$(NF-1)};END{print "************************"}' 1.txt
$ awk 'BEGIN{ FS=":";print "Login_shell\tLogin_home\n*******************"};{print $NF"\t"$(NF-1)};END{print "************************"}' 1.txt

Login_shell        Login_home
************************
/bin/bash        /root
/sbin/nologin        /bin
/sbin/nologin        /sbin
/sbin/nologin        /var/adm
/sbin/nologin        /var/spool/lpd
/bin/bash        /home/redhat
/bin/bash        /home/user01
/sbin/nologin        /var/named
/bin/bash        /home/u01
/bin/bash        /home/YUNWEI
************************************

awk和正则表达式的综合运用

运算符说明
==等于
!=不等于
>大于
<小于
>=大于等于
<=小于等于
~匹配
!~不匹配
!逻辑非
&&逻辑与
||逻辑或

使用示例

# 从第一行开始匹配到以lp开头行
$ awk -F: 'NR==1,/^lp/{print $0 }' passwd  

# 从第一行到第5行          
$ awk -F: 'NR==1,NR==5{print $0 }' passwd

# 从以lp开头的行匹配到第10行       
$ awk -F: '/^lp/,NR==10{print $0 }' passwd 

# 从以root开头的行匹配到以lp开头的行       
$ awk -F: '/^root/,/^lp/{print $0}' passwd

# 打印以root开头或者以lp开头的行            
$ awk -F: '/^root/ || /^lp/{print $0}' passwd
$ awk -F: '/^root/;/^lp/{print $0}' passwd

# 显示5-10行   
$ awk -F':' 'NR>=5 && NR<=10 {print $0}' /etc/passwd     
$ awk -F: 'NR<10 && NR>5 {print $0}' passwd 

# 打印30-39行以bash结尾的内容:
$ awk 'NR>=30 && NR<=39 && $0 ~ /bash$/{print $0}' passwd 
stu1:x:500:500::/home/stu1:/bin/bash
yunwei:x:501:501::/home/yunwei:/bin/bash
user01:x:502:502::/home/user01:/bin/bash
user02:x:503:503::/home/user02:/bin/bash
user03:x:504:504::/home/user03:/bin/bash

$ awk 'NR>=3 && NR<=8 && /bash$/' 1.txt  
stu7:x:1007:1007::/rhome/stu7:/bin/bash
stu8:x:1008:1008::/rhome/stu8:/bin/bash
stu9:x:1009:1009::/rhome/stu9:/bin/bash

# 打印文件中1-5并且以root开头的行
$ awk 'NR>=1 && NR<=5 && $0 ~ /^root/{print $0}' 1.txt
root:x:0:0:root:/root:/bin/bash
$ awk 'NR>=1 && NR<=5 && $0 !~ /^root/{print $0}' 1.txt
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

# 理解;号和||的含义:
$ awk 'NR>=3 && NR<=8 || /bash$/' 1.txt
$ awk 'NR>=3 && NR<=8;/bash$/' 1.txt

# 打印IP地址
$ ifconfig eth0|awk 'NR>1 {print $2}'|awk -F':' 'NR<2 {print $2}'    
$ ifconfig eth0|grep Bcast|awk -F':' '{print $2}'|awk '{print $1}'
$ ifconfig eth0|grep Bcast|awk '{print $2}'|awk -F: '{print $2}'
$ ifconfig eth0|awk NR==2|awk -F '[ :]+' '{print $4RS$6RS$8}'
$ ifconfig eth0|awk -F"[ :]+" '/inet addr:/{print $4}'

# 显示可以登录操作系统的用户所有信息     从第7列匹配以bash结尾,输出整行(当前行所有的列)
$ awk '/bash$/{print $0}'    /etc/passwd
$ awk '/bash$/{print $0}' /etc/passwd
$ awk '/bash$/' /etc/passwd
$ awk -F: '$7 ~ /bash/' /etc/passwd
$ awk -F: '$NF ~ /bash/' /etc/passwd
$ awk -F: '$0 ~ /bash/' /etc/passwd
$ awk -F: '$0 ~ /\/bin\/bash/' /etc/passwd

# 显示可以登录系统的用户名
$ awk -F: '$0 ~ /\/bin\/bash/{print $1}' /etc/passwd

# 打印出系统中普通用户的UID和用户名
$ awk -F: 'BEGIN{print "UID\tUSERNAME"} {if($3>=500 && $3 !=65534 ) {print $3"\t"$1} }' /etc/passwdUID    USERNAME
$ awk -F: '{if($3 >= 500 && $3 != 65534) print $1,$3}' a.txt 

awk脚本编程

awk脚本编程之流程控制语句

if语句

基本语法:awk 选项 ‘正则,地址定位{awk语句}’ 文件名

$ awk -F: '{if($3>=500 && $3<=60000) {print $1,$3} }' passwd
$ awk -F: '{if($3==0) {print $1"是管理员"} }' passwd
$ awk 'BEGIN{if('$(id -u)'==0) {print "admin"} }'

if…else语句

$ awk -F: '{ if($3>=500 && $3 != 65534) {print $1"是普通用户"} else {print $1,"不是普通用户"}}' passwd 
$ awk 'BEGIN{if( '$(id -u)'>=500 && '$(id -u)' !=65534 ) {print "是普通用户"} else {print "不是普通用户"}}'

if…elif…else语句

$ awk -F: '{ if($3==0) {print $1,":是管理员"} else if($3>=1 && $3<=499 || $3==65534 ) {print $1,":是系统用户"} else {print $1,":是普通用户"}}'
$ awk -F: '{ if($3==0) {i++} else if($3>=1 && $3<=499 || $3==65534 ) {j++} else {k++}};END{print "管理员个数为:"i "\n系统用户个数为:"j"\n普通用户的个数为:"k }'
$ awk -F: '{if($3==0) {print $1,"is admin"} else if($3>=1 && $3<=499 || $3==65534) {print $1,"is sys users"} else {print $1,"is general user"} }' a.txt 
$ awk -F: '{  if($3==0) {print $1":管理员"} else if($3>=1 && $3<500 || $3==65534 ) {print $1":是系统用户"} else {print $1":是普通用户"}}'   /etc/passwd
$ awk -F: '{if($3==0) {i++} else if($3>=1 && $3<500 || $3==65534){j++} else {k++}};END{print "管理员个数为:" i RS "系统用户个数为:"j RS "普通用户的个数为:"k }' /etc/passwd
$ awk -F: '{ if($3==0) {print $1":是管理员"} else if($3>=500 && $3!=65534) {print $1":是普通用户"} else {print $1":是系统用户"}}' passwd 
$ awk -F: '{if($3==0){i++} else if($3>=500){k++} else{j++}} END{print i; print k; print j}' /etc/passwd
$ awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员个数: "i; print "普通用个数: "k; print "系统用户: "j}' /etc/passwd 

# 如果是普通用户打印默认shell,如果是系统用户打印用户名
$ awk -F: '{if($3>=1 && $3<500 || $3 == 65534) {print $1} else if($3>=500 && $3<=60000 ) {print $NF} }' /etc/passwd
awk脚本编程之循环语句

for循环语法

# 打印1~5
$ for ((i=1;i<=5;i++));do echo $i;done
$ awk 'BEGIN { for(i=1;i<=5;i++) {print i} }'

# 打印1~10中的奇数
$ for ((i=1;i<=10;i+=2));do echo $i;done|awk '{sum+=$0};END{print sum}'
$ awk 'BEGIN{ for(i=1;i<=10;i+=2) {print i} }'
$ awk 'BEGIN{ for(i=1;i<=10;i+=2) print i }'

# 计算1-5的和
$ awk 'BEGIN{sum=0;for(i=1;i<=5;i++) sum+=i;print sum}'
$ awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);{print sum}}'
$ awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);print sum}'

while循环语句

# 打印1-5
$ i=1;while (($i<=5));do echo $i;let i++;done
$ awk 'BEGIN { i=1;while(i<=5) {print i;i++} }'

# 打印1~10中的奇数
$ awk 'BEGIN{i=1;while(i<=10) {print i;i+=2} }'

# 计算1-5的和
$ awk 'BEGIN{i=1;sum=0;while(i<=5) {sum+=i;i++}; print sum }'
$ awk 'BEGIN {i=1;while(i<=5) {(sum+=i) i++};print sum }'

嵌套循环语句

$ awk 'BEGIN{ for(y=1;y<=5;y++) {for(x=1;x<=y;x++) {printf x} ;print } }'
$ awk 'BEGIN { for(y=1;y<=5;y++) { for(x=1;x<=y;x++) {printf x};print} }'
$ awk 'BEGIN{ y=1;while(y<=5) { for(x=1;x<=y;x++) {printf x};y++;print}}'
$ awk 'BEGIN{for(y=1;y<=9;y++) { for(x=1;x<=y;x++) {printf x"*"y"="x*y"\t"};print} }'
$ awk 'BEGIN{for(y=1;y<=9;y++) { for(x=1;x<=y;x++) printf x"*"y"="x*y"\t";print} }'
$ awk 'BEGIN{i=1;while(i<=9){for(j=1;j<=i;j++) {printf j"*"i"="j*i"\t"};print;i++ }}'
$ awk 'BEGIN{for(i=1;i<=9;i++){j=1;while(j<=i) {printf j"*"i"="i*j"\t";j++};print}}'
$ awk 'BEGIN{for(i=1;i<=5;i++) {if(i==3) break;print i} }'
$ awk 'BEGIN{for(i=1;i<=5;i++){if(i==3) continue;print i}}'

awk算术运算

# + - * / %(模) ^(幂2^3)
# 可以在模式中执行计算,awk都将按浮点数方式执行算术运算
$ awk 'BEGIN{print 1+1}'
$ awk 'BEGIN{print 1**1}'
$ awk 'BEGIN{print 2**3}'
$ awk 'BEGIN{print 2/3}'

awk统计案例

统计系统中各种类型的shell
$ awk -F: '{ shells[$NF]++ };END{for (i in shells) {print i,shells[i]} }' /etc/passwd
统计网站访问状态
$ ss -antp|grep 80|awk '{states[$1]++};END{for(i in states){print i,states[i]}}'
$ ss -an |grep :80 |awk '{states[$2]++};END{for(i in states){print i,states[i]}}'
$ ss -an |grep :80 |awk '{states[$2]++};END{for(i in states){print i,states[i]}}' |sort -k2 -rn
统计访问网站的每个IP的数量
$ netstat -ant |grep :80 |awk -F: '{ip_count[$8]++};END{for(i in ip_count){print i,ip_count[i]} }' |sort
$ ss -an |grep :80 |awk -F":" '!/LISTEN/{ip_count[$(NF-1)]++};END{for(i in ip_count){print i,ip_count[i]}}' |sort -k2 -rn |head
统计网站日志中的PV量
# 统计Apache/Nginx日志中某一天的PV量  <统计日志>
$ grep '27/Jul/2017' mysqladmin.cc-access_log |wc -l

# 统计Apache/Nginx日志中某一天不同IP的访问量 <统计日志>
$ grep '27/Jul/2017' mysqladmin.cc-access_log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' | sort -k2 -rn | head
$ grep '07/Aug/2017' access.log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' |awk '$2>100' | sort -k2 -rn

名词解释:

  • 名词:PV=PageView (网站浏览量)
    说明:指页面的浏览次数,用以衡量网站用户访问的网页数量。多次打开同一页面则浏览量累计。用户每打开一个页面便记录1次PV。
  • 名词:VV = Visit View(访问次数)
    说明:从访客来到您网站到最终关闭网站的所有页面离开,计为1次访问。若访客连续30分钟没有新开和刷新页面,或者访客关闭了浏览器,则被计算为本次访问结束。
  • 独立访客(UV)
    名词:UV= Unique Visitor(独立访客数)
    说明:1天内相同的访客多次访问您的网站只计算1个UV。
  • 独立IP(IP)
    名词:IP=独立IP数
    说明:指1天内使用不同IP地址的用户访问网站的数量。同一IP无论访问了几个页面,独立IP数均为1

文章来源:https://blog.csdn.net/qq_45157350/article/details/135169904
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。