三剑客 -- awk

[root@hass-11 ~]# vmstat
进程				内存				交换分区	磁盘		系统		CPU
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
																	
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 1140540   2076 625804    0    0     1     0   28   28  0  0 100  0  0

小用法,使用awk来对文件随机抽取n行

awk 'BEGIN{srand()} {print rand()"\t"$0}' input_file | sort -nk 1 | head -n line_num | awk -F "\t" '{print $2}' 

简介

awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。

awk有3个不同版本: awk、nawk和gawk,未作特别说明,一般指gawk,gawk 是 AWK 的 GNU 版本。

awk其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。实际上 AWK 的确拥有自己的语言: AWK 程序设计语言 , 三位创建者已将它正式定义为样式扫描和处理语言。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。

命令格式

awk 选项 '操作' 文件
或
其他命令 | awk 选项 '操作'

#花括号 {}不需要在程序中始终出现,但它们用于根据特定的模式'对一系列指令进行分组'。
#要表示的正则表达式,用'斜杠'括起来
#通常,awk是以文件的一行为处理单位的。awk每接收文件的一行,然后执行相应的命令,来处理文本。
#awk 相当于 grep + cut
#awk 逐行读取,以指定分隔符分割字符串,赋值给变量

选项

-F 
# 指定输入分隔符,awk会以该分隔符为准来切定位到的行,默认以空格或连续的空格为分隔符

操作

操作=定位+命令

定位
	行号
	正则
    
命令

在读入所有行之前进行的操作        读一行执行一次的操作      所有行都读完后执行的操作
BEGIN{}                         定位{}                     END{}
条件类型 条 件 说 明
awk保留字 BEGIN 在 awk 程序一开始,尚未读取任何数据之前执行。BEGIN 后的动作只在程序开始时执行一次
awk保留字 END 在 awk 程序处理完所有数据,即将结束时执行?END 后的动作只在程序结束时执行一次
关系运算符 > 大于
< 小于
>= 大于等于
<= 小于等于
== 等于。用于判断两个值是否相等。如果是给变童赋值,则使用"=”
!= 不等于
A~B 判断字符串 A 中是否包含能匹配 B 表达式的子字符串
A!~B 判断字符串 A 中是否不包含能匹配 B 表达式的子字符串
正则表达式 /正则/ 如果在“//”中可以写入字符,则也可以支持正则表达式

调用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)跟上面的是一样的。

awk中的正则

<、<=、==、!=、>=、~匹配正则表达式(操作列)、!~不匹配正则表达式(取反)

匹配:awk '{if ($4~/ASIMA/) print $0}' temp 表示如果第四个域包含 ASIMA,就打印整条

精确匹配:awk '$3=="48" {print $0}' temp    只打印第 3 域等于"48"的记录

不匹配:  awk '$0 !~ /ASIMA/' temp      打印整条不包含 ASIMA 的记录

不等于:  awk '$1 != "asima"' temp

小于:    awk '{if ($1<$2) print $1 "is smaller"}' temp

设置大小写: awk '/[Gg]reen/' temp      打印整条包含 Green,或者 green 的记录

任意字符: awk '$1 ~/^...a/' temp    打印第 1 域中第四个字符是 a 的记录,符号’^’代表行首,符合’.’代表任意字符

或关系匹配: awk '$0~/(abc)|(efg)/' temp   使用| 时,语句尽量括起来
并且关系匹配  awk -F: '$0~/(root)/ && $0~/(x)/'

AND 与关系:  awk '{if ( $1=="a" && $2=="b" ) print $0}' temp
		   awk -F: '$3 == $4 {print $0}'

OR 或关系:   awk '{if ($1=="a" || $1=="b") print $0}' temp

awk 的简单实用

awk '{print $1}' /etc/passwd
awk -F ':' '{print $1}' /etc/passwd				#-F指定域分隔符为':'
awk -F ':' '{print $1"\t"$7}' /etc/passwd		 #拼接域
awk -F ':' '{$4=$3-$2; print $4}'				#拼接域
awk -F: '/root/' /etc/passwd					#过滤
awk -F: '/^root/' /etc/passwd
awk -F: '/root/{print $7}' /etc/passwd 
awk -F: 'NR==3{print $7}' /etc/passwd 
awk -F: 'NR>=3 && NR<=5{print $7}' /etc/passwd 
awk -F: 'NR<=3 || NR>=5{print $7}' /etc/passwd 
awk 'BEGIN{print "hh"} {print $1} END{print "hh"}'

# awk 中变量可以直接打印,不需要使用$
# awk中字符串的拼接使用" ",可以在引号内添加空格,也可以使用逗号来加空格
# awk中,正则要放到斜杠/ ,或者指定的边界内

awk 'BEGIN{FS=":";}/root/{print $0} ' a.txt

[root@egon day09]# awk 'BEGIN{FS=":";OFS="-"}{print NR,$0}' 6.txt 
1-root:x:0:0:root:/root:/bin/bash
2-bin:x:1:1:bin:/bin:/sbin/nologin
3-daemon:x:2:2:root:/sbin:/sbin/nologin
4-adm:x:3:4:adm:/var/adm:/sbin/nologin
5-lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
                        
[root@egon day09]# awk 'BEGIN{FS=":";OFS="-"}/root/{print NR,$1,$3,$5}' 6.txt 
1-root-0-root
3-daemon-2-root

[root@egon day09]# awk 'BEGIN{FS=":";}/root/{print $0} ' 6.txt
root:x:0:0:root:/root:/bin/bash
daemon:x:2:2:root:/sbin:/sbin/nologin
[root@egon day09]# awk 'BEGIN{FS=":";}/root/{print NF} ' 6.txt
7
7
[root@egon day09]# awk 'BEGIN{FS=":";}/root/{print $NF} ' 6.txt
/bin/bash
/sbin/nologin
[root@egon day09]# awk 'BEGIN{FS=":";}/root/{print $(NF-1)} ' 6.txt
/root
/sbin

#会显示每列的内容
awk '(tot+=$3);END {print tot}' temp             
awk '{(tot+=$3)};END {print tot}' temp 

#文件长度相加
ls -l|awk '/^[^d]/ {print $9"\t"$5} {tot+=$5} END{print "totKB:" tot}'

#awk内,支持变量直接运算,相当于expr

如果只是显示/etc/passwd的账户和账户对应的shell,而账户与shell之间以逗号分割,而且在所有行添加列名name,shell,在最后一行添加"blue,/bin/nosh"。

cat /etc/passwd |awk  -F ':'  'BEGIN {print "name,shell"}  {print $1","$7} END {print "blue,/bin/nosh"}'
name,shell
root,/bin/bash
daemon,/bin/sh
bin,/bin/sh
sys,/bin/sh
....
blue,/bin/nosh

#awk工作流程是这样的:先执行BEGING,然后读取文件,读入有/n换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域,$1表示第一个域,$n表示第n个域,随后开始执行模式所对应的动作action。接着开始读入第二条记录······直到所有的记录都读完,最后执行END操作。

awk内置变量

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

ARGC               命令行参数个数
ARGV               命令行参数排列
ENVIRON            支持队列中系统环境变量的使用
FILENAME           awk浏览的文件名
FNR                浏览文件的记录数
FS                 设置输入域分隔符,等价于命令行 -F选项
NF                 浏览记录的域的个数,列数,列
NR                 已读行的记录数,行号
OFS                输出域分隔符,默认是空格
ORS                输出记录分隔符
RS                 控制记录分隔符

NR>=1 && NR<=3	   相当于sed中的 1,3中的行号定位
;				分割多条命令

此外,$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

[root@egon day09]# awk 'BEGIN{FS=":";}/root/{print $1,$3} ' 6.txt
root 0
daemon 2
[root@egon day09]# awk 'BEGIN{FS=":";}/root/{print "用户名:",$1,"用户id:",$3} ' 6.txt
用户名: root 用户id: 0
用户名: daemon 用户id: 2

使用printf替代print,可以让代码更加简洁,易读

awk  -F ':' '{printf("filename:%10s,linenumber:%s,columns:%s,linecontent:%s\n",FILENAME,NR,NF,$0)}' /etc/passwd
 
[root@egon day09]# date | awk '{printf "年:%s\n月:%s\n",$NF,$2}'
年:2020
月:Sep
[root@egon day09]# awk 'BEGIN{FS=":";}/root/{printf "用户名:%s 用户id:%s\n",$1,$3} ' 6.txt
用户名:root 用户id:0
用户名:daemon 用户id:2

# %-15s 表示占位符,左对齐,站位15个空格
[root@egon day09]# awk -F: '{printf "用户名:%-15s|用户id:%-15s|用户组id:%-15s\n",$1,$3,$4}' /etc/passwd

[root@hass-11 ~]# df |awk 'NR>1{printf "对象:%s\t已用百分比:%s\n",$1,$5}'
对象:devtmpfs	已用百分比:0%
对象:tmpfs	已用百分比:0%

print和printf

awk中同时提供了print和printf两种打印输出的函数。

其中print函数的参数可以是'变量、数值或者字符串'。字符串必须用'双引号'引用,参数用'逗号分隔'。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与'输出文件的分隔符'的作用是一样的,只是后者是空格而已。

printf函数,其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。

定位

#正则表达式
[root@hass-11 ~]# awk '/root/{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk '/^root/{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk '$2 ~ /root/{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk '$2 !~ /root/{print NR,$0}' /etc/passwd

#比较表达式
[root@hass-11 ~]# awk 'NR>=3 && NR<=5{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk 'NR>=3 || NR<=5{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk -F: '$3 > 500{print $1,$3}' /etc/passwd
[root@hass-11 ~]# awk -F: '$1 !=root{print $1,$3}' /etc/passwd

#行号
[root@hass-11 ~]# awk 'NR=5{print NR,$0}' /etc/passwd

#幂数运算,+ - * / %(模) ^(幂2^3)
[root@hass-11 ~]# awk -F: '$4*12 >40{print $0}' /etc/passwd
[root@hass-11 ~]# awk -F: 'NR%2 == 1{print $0}' /etc/passwd	#取奇数行

#且 或 非
[root@hass-11 ~]# awk 'NR>=3 && NR<=5{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk 'NR>=5 || NR<=3{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk '!(NR>=3 && NR<=4){print NR,$0}' a.txt 
[root@hass-11 ~]# awk 'NR>=3,NR<=5{print NR,$0}' /etc/passwd	#
[root@hass-11 ~]# awk 'NR>=3,/yy/{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk '/root/,NR<=5{print NR,$0}' /etc/passwd
[root@hass-11 ~]# awk '/root/,/yy/{print NR,$0}' /etc/passwd #可以多次匹配

#awk中,定位既可以在{}外,也可以在{}内,只不过在{}内要加if判断

awk 内置字符串函数

gsub(r,s)              在整个$0中用s替代r  
  
awk 'gsub(/name/,"xingming") {print $0}' temp  
  
gsub(r,s,t)           在整个t中用s替代r  
  
index(s,t)             返回s中字符串t的第一位置  
  
awk 'BEGIN {print index("Sunny","ny")}' temp     返回4  
  
length(s)               返回s的长度  
  
match(s,r)             测试s是否包含匹配r的字符串  
  
awk '$1=="J.Lulu" {print match($1,"u")}' temp    返回4  
  
split(s,a,fs)         在fs上将s分成序列a   
  
awk 'BEGIN {print split("12#345#6789",myarray,"#")"' 
 
返回3,同时myarray[1]="12", myarray[2]="345", myarray[3]="6789"  
  
sprint(fmt,exp)        返回经fmt格式化后的exp  
  
sub(r,s)   从$0中最左边最长的子串中用s代替r(只更换第一遇到的匹配字符串)  
  
substr(s,p)         返回字符串s中从p开始的后缀部分  
  
substr(s,p,n)       返回字符串s中从p开始长度为n的后缀部分

awk编程

变量和赋值

除了awk的内置变量,awk还可以自定义变量。

下面统计/etc/passwd的账户人数

awk '{count++;print $0;} END{print "user count is ", count}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
......
user count is  40

#count是自定义变量。其实print只是一个语句,多个语句,以;号隔开。

这里没有初始化count,虽然默认是0,但是妥当的做法还是初始化为0:

awk 'BEGIN {count=0;print "[start]user count is ", count} {count=count+1;print $0;} END{print "[end]user count is ", count}' /etc/passwd
[start]user count is  0
root:x:0:0:root:/root:/bin/bash
...
[end]user count is  40

统计某个文件夹下的文件占用的字节数

ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size}'
[end]size is  8657198

如果以M为单位显示:

ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size/1024/1024,"M"}' 
[end]size is  8.25889 M

#注意,统计不包括文件夹的子目录。

条件语句

awk中的条件语句是从C语言中借鉴来的,见如下声明方式:

if (expression) {
    statement;
    statement;
    ... ...
}

if (expression) {
    statement;
} else {
    statement2;
}

if (expression) {
    statement1;
} else if (expression1) {
    statement2;
} else {
    statement3;
}

# 1、单分支
[root@egon day10]# awk -F: '$3 > 1000{print $0}' /etc/passwd
awk -F: '{if ($3>1000) {count++;}}END{print count}' /etc/passwd

# 2、双分支
awk -F: '{if ($3==0) {x++} else {y++}}END{printf "管理用户个数:%s 其他用户个数:%s\n",x,y}' /etc/passwd

# 3、多分支
awk -F: '{if ($3==0) {x++} else if($3>1000) {y++} else {z++}}END{printf "管理用户个数:%s 普通用户个数:%s 系统用户:%s\n",x,y,z}' /etc/passwd

#awk中判断结束不需要使用fi 结尾
#awk中,{}可以嵌套
#分号表示不同命令的分割

统计某个文件夹下的文件占用的字节数,过滤不是4096大小的文件(一般都是文件夹):

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"}' 
[end]size is  8.22339 M

循环语句

awk中的循环语句同样借鉴于C语言,支持while、do/while、for、break、continue,这些关键字的语义和C语言中的语义完全相同。

#循环
[root@hass-11 ~]# awk -F: '/root/{count=0;while(count < 3) {count++;print $0}}' /etc/passwd
[root@hass-11 ~]# awk -F: '/root/{for(i=0;i<3;i++) {print $0}}' /etc/passwd


#循环+判断+continue
[root@hass-11 ~]# awk -F: '/root/{count=0;while(count < 3) {count++;if (count==1) {continue};print $0}}' /etc/passwd
[root@hass-11 ~]# awk -F: '/root/{for(i=0;i<3;i++) {if(i==1) {continue};print $0}}' /etc/passwd

#循环+判断+break
[root@hass-11 ~]# awk -F: '{count=0;while(count < 3) {count++;if (count==2) {break};print $0}}' /etc/passwd
[root@hass-11 ~]# awk -F: '/root/{for(i=0;i<3;i++) {if(i==1) {break};print $0}}' /etc/passwd

#死循环
[root@hass-11 ~]# awk -F: '{while(1 !=2) {print $0}}' /etc/passwd


#计算的话,count一定要定义在 BEGIN
#循环的话,count一定要定义在 第二段
# xxx=0,在awk中可以省略,直接使用

数组

因为awk中数组的下标可以是数字和字母,数组的下标通常被称为关键字(key)。值和关键字都存储在内部的一张针对key/value应用hash的表格里。由于hash不是顺序存储,因此在显示数组内容时会发现,它们并不是按照你预料的顺序显示出来的。数组和变量一样,都是在使用时自动创建的,awk也同样会自动判断其存储的是数字还是字符串。一般而言,awk中的数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹配的次数等等。

显示/etc/passwd的账户

#普通数组的定义和遍历
awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;}; END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd
0 root
1 daemon
2 bin
3 sys
4 sync
5 games
......


#name[0] 表示数组中的第一个元素
#awk定义的数组,在命令行中看不到

数组+for循环的另外一种格式

awk -F: 'BEGIN{i=0} {user[i++]=$1}END{print user[0]}' /etc/passwd
    
awk -F: '{user[i++]=$1}END{print user[0]}' /etc/passwd

遍历数组

# 有序遍历
awk -F: 'BEGIN{i=0} {user[i++]=$1} END{for(j=0;j<NR;j++) {print j,user[j]}}' /etc/passwd
awk -F: 'BEGIN{i=1} {user[i++]=$1} END{for(j=1;j<=NR;j++) {print j,user[j]}}' /etc/passwd

for ((i=0;i<28;i++))	#固定格式,对应user0 user1 user2
do
   echo ${user[$i]}
done

# 无序遍历
awk -F: 'BEGIN{i=1}{user[i++]=$1} END{for (x in user) {print user[x]}}' /etc/passwd
awk -F: 'BEGIN{i=0}{user[$1]=$3} END{for (k in user) {print k,user[k]}}' /etc/passwd
    
#普通数组既可以有序遍历,又可以无序遍历
#关联数组只能无序遍历

脚本中的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  
}

使用pid文件限制重复启动进程

[root@egon day10]# cat proc.sh 
#!/bin/bash
pidfile='/tmp/proc.pid'

if [ -f $pidfile ];then
    pid=`cat $pidfile`
    ps $pid &>/dev/null
    [ $? -eq 0 ] && echo "脚本正在运行..." && exit 1
fi

echo "$$" > $pidfile

echo "脚本正在运行。。。"
sleep 500
echo "脚本运行完毕。。。"

rm -rf $pidfile

shell队列实现并发控制

https://blog.51cto.com/egon09/1754317

awk编程的内容极多,这里只罗列简单常用的用法,更多请参考 http://www.gnu.org/software/gawk/manual/gawk.html

https://blog.51cto.com/egon09

上一篇:5G系列协议--3GPP--Release 16--TS 38.101-1--NR:用户设备(UE)无线电发射和接收--独立组网-1


下一篇:Universal Link