awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。
awk [options] 'script' var=value file(s)
awk [options] -f scriptfile var=value file
命令选项
变量:内置变量和自定义变量,变量前加-v
的选项
说明:[A][N][P][G]
表示第一个支持变量的工具,[A]=awk、[N]=nawk、[P]=POSIXawk、[G]=gawk
(1)变量说明
[A] :
NF 表示字段数,在执行过程中对应于当前的字段数,$NF
引用最后一列,$(NF-1)
引用倒数第2列。
NR 表示记录数,在执行过程中对应于当前的行号,后可跟多个文件,第二个文件行号继续从第一个文件最后行号开始。
OFMT 数字的输出格式(默认值是%.6g)。
OFS 输出字段分隔符(默认值是一个空格)。
ORS 输出记录分隔符(默认值是一个换行符)。
RS 记录分隔符(默认是一个换行符)。
FS 字段分隔符(默认是任何空格)。
FILENAME 当前输入文件的名。
[N] :
ARGC 命令行参数的数目。
ARGV 包含命令行参数的数组。
ERRNO 最后一个系统错误的描述。
RSTART 由match函数所匹配的字符串的第一个位置。
RLENGTH 由match函数所匹配的字符串的长度。
SUBSEP 数组下标分隔符(默认值是34)。
[G]:
ARGIND 命令行中当前文件的位置(从0开始算)。
IGNORECASE 如果为真,则进行忽略大小写的匹配。
CONVFMT 数字转换格式(默认值为%.6g)。
FIELDWIDTHS 字段宽度列表(用空格键分隔)。
[P] :
ENVIRON 环境变量关联数组。
FNR 同NR,但相对于当前文件。
(2)示例
[root@along ~]# cat awkdemo
hello:world
linux:redhat:lalala:hahaha
along:love:youou
[root@along ~]# awk -v FS=':' '{print $1,$2}' awkdemo #FS指定输入分隔符
hello world
linux redhat
along love
[root@along ~]# awk -v FS=':' -v OFS='---' '{print $1,$2}' awkdemo #OFS指定输出分隔符
hello---world
linux---redhat
along---love
[root@along ~]# awk -v RS=':' '{print $1,$2}' awkdemo
hello
world linux
redhat
lalala
hahaha along
love
you
[root@along ~]# awk -v FS=':' -v ORS='---' '{print $1,$2}' awkdemo
hello world---linux redhat---along love---
[root@along ~]# awk -F: '{print NF}' awkdemo
2
4
3
[root@along ~]# awk -F: '{print $(NF-1)}' awkdemo #显示倒数第2列
hello
lalala
love
[root@along ~]# awk '{print NR}' awkdemo awkdemo1
1
2
3
4
5
[root@along ~]# awk END'{print NR}' awkdemo awkdemo1
5
[root@along ~]# awk '{print FNR}' awkdemo awkdemo1
1
2
3
1
2
[root@along ~]# awk '{print FILENAME}' awkdemo
awkdemo
awkdemo
awkdemo
[root@along ~]# awk 'BEGIN {print ARGC}' awkdemo awkdemo1
3
[root@along ~]# awk 'BEGIN {print ARGV[0]}' awkdemo awkdemo1
awk
[root@along ~]# awk 'BEGIN {print ARGV[1]}' awkdemo awkdemo1
awkdemo
[root@along ~]# awk 'BEGIN {print ARGV[2]}' awkdemo awkdemo1
awkdemo1
自定义变量( 区分字符大小写)
(1)-v var=value
① 先定义变量,后执行动作print
[root@along ~]# awk -v name="along" -F: '{print name":"$0}' awkdemo
along:hello:world
along:linux:redhat:lalala:hahaha
along:along:love:you
② 在执行动作print后定义变量
[root@along ~]# awk -F: '{print name":"$0;name="along"}' awkdemo
:hello:world
along:linux:redhat:lalala:hahaha
along:along:love:you
(2)在program 中直接定义
可以把执行的动作放在脚本中,直接调用脚本 -f
[root@along ~]# cat awk.txt
{name="along";print name,$1}
[root@along ~]# awk -F: -f awk.txt awkdemo
along hello
along linux
along along
作为一种程序设计语言所应具有的特点之一,awk支持多种运算,这些运算与C语言提供的基本相同。awk还提供了一系列内置的运算函数(如log、sqr、cos、sin等)和一些用于对字符串进行操作(运算)的函数(如length、substr等等)。这些函数的引用大大的提高了awk的运算功能。作为对条件转移指令的一部分,关系判断是每种程序设计语言都具备的功能,awk也不例外,awk中允许进行多种测试,作为样式匹配,还提供了模式匹配表达式(匹配)和!(不匹配)。作为对测试的一种扩充,awk也支持用逻辑运算符。
运算符 | 描述 |
---|---|
+ - | 加,减 |
* / & | 乘,除与求余 |
+ - ! | 一元加,减和逻辑非 |
^ *** | 求幂 |
++ – | 增加或减少,作为前缀或后缀 |
运算符 | 描述 |
---|---|
|| | 逻辑或 |
&& | 逻辑与 |
运算符 | 描述 |
---|---|
~ !~ | 匹配正则表达式和不匹配正则表达式 |
^ | 行首 |
$ | 行尾 |
. | 除了换行符以外的任意单个字符 |
* | 前导字符的零个或多个 |
.* | 所有字符 |
[] | 字符组内的任一字符 |
[^] | 对字符组内的每个字符取反(不匹配字符组内的每个字符) |
^[^] |
非字符组内的字符开头的行 |
[a-z] | 小写字母 |
[A-Z] | 大写字母 |
[a-Z] | 小写和大写字母 |
[0-9] | 数字 |
\< |
单词头单词一般以空格或特殊字符做分隔,连续的字符串被当做单 |
\> |
单词尾 |
注意:正则需要用/正则/包围住
运算符 | 描述 |
---|---|
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
!= | 不等于 |
== | 等于 |
运算符 | 描述 |
---|---|
$ | 字段引用 |
空格 | 字符串连接符 |
?: | C条件表达式 |
in | 数组中是否存在某键值 |
常用内置函数:
tolower()
:字符转为小写。length()
:返回字符串长度。substr()
:返回子字符串。sin()
:正弦。cos()
:余弦。sqrt()
:平方根。rand()
:随机数。在linux awk的while、do-while和for语句中允许使用break,continue语句来控制流程走向,也允许使用exit这样的语句来退出。break中断当前正在执行的循环并跳到循环外执行下一条语句。if 是流程选择用法。awk中,流程控制语句,语法结构,与c语言类型。有了这些语句,其实很多shell程序都可以交给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
(1)语法
if(condition){statement;…}[else statement] 双分支
if(condition1){statement1}else if(condition2){statement2}else{statement3} 多分支
(2)使用场景:对awk 取得的整行或某个字段做条件判断
(3)示例
[root@along ~]# awk -F: '{if($3>10 && $3<1000)print $1,$3}' /etc/passwd
operator 11
games 1
[root@along ~]# awk -F: '{if($NF=="/bin/bash") print $1,$NF}' /etc/passwd
root /bin/bash
along /bin/bash
---输出总列数大于3的行
[root@along ~]# awk -F: '{if(NF>2) print $0}' awkdemo
linux:redhat:lalala:hahaha
along:love:you
---第3列>=1000为Common user,反之是root or Sysuser
[root@along ~]# awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else{printf "root or Sysuser: %s\n",$1}}' /etc/passwd
root or Sysuser: root
root or Sysuser: bin
Common user: along
---磁盘利用率超过40的设备名和利用率
[root@along ~]# df -h|awk -F% '/^\/dev/{print $1}'|awk '$NF > 40{print $1,$NF}'
/dev/mapper/cl-root 43
---test=100和>90为very good; 90>test>60为good; test<60为no pass
[root@along ~]# awk 'BEGIN{ test=100;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
very good
[root@along ~]# awk 'BEGIN{ test=80;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
good
[root@along ~]# awk 'BEGIN{ test=50;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
no pass
(1)语法
while(condition){statement;…}
注:条件“真”,进入循环;条件“假”, 退出循环
(2)使用场景
对一行内的多个字段逐一类似处理时使用
对数组中的各元素逐一处理时使用
(3)示例
---以along开头的行,以:为分隔,显示每一行的每个单词和其长度
[root@along ~]# awk -F: '/^along/{i=1;while(i<=NF){print $i,length($i); i++}}' awkdemo
along 5
love 4
you 3
---以:为分隔,显示每一行的长度大于6的单词和其长度
[root@along ~]# awk -F: '{i=1;while(i<=NF) {if(length($i)>=6){print $i,length($i)}; i++}}' awkdemo
redhat 6
lalala 6
hahaha 6
---计算1+2+3+...+100=5050
[root@along ~]# awk 'BEGIN{i=1;sum=0;while(i<=100){sum+=i;i++};print sum}'
5050
(1)语法
do{statement;…}while(condition)
意义:无论真假,至少执行一次循环体
(2)计算1+2+3+…+100=5050
[root@along ~]# awk 'BEGIN{sum=0;i=1;do{sum+=i;i++}while(i<=100);print sum}'
5050
(1)语法
for(expr1;expr2;expr3) {statement;…}
(2)特殊用法:遍历数组中的元素
for(var in array) {forbody}
(3)示例
---显示每一行的每个单词和其长度
[root@along ~]# awk -F: '{for(i=1;i<=NF;i++) {print$i,length($i)}}' awkdemo
hello 5
world 5
linux 5
redhat 6
lalala 6
hahaha 6
along 5
love 4
you 3
---求男m、女f各自的平均
[root@along ~]# cat sort.txt
score
[m=>170]
xiaoming m 90
xiaohong f 93
xiaohei m 80
xiaofang f 99
[root@along ~]# awk '{m[$2]++;score[$2]+=$3}END{for(i in m){printf "%s:%6.2f\n",i,score[i]/m[i]}}' sort.txt
m: 85.00
f: 96.00
break 当 break 语句用于 while 或 for 语句时,导致退出程序循环。
continue 当 continue 语句用于 while 或 for 语句时,使程序循环移动到下一个迭代。
next 能能够导致读入下一个输入行,并返回到脚本的顶部。这可以避免对当前输入行执行其他的操作过程。
exit 语句使主输入循环退出并将控制转移到END,如果END存在的话。如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行。
数组是awk的灵魂,处理文本中最不能少的就是它的数组处理。因为数组索引(下标)可以是数字和字符串在awk中数组叫做关联数组(associative arrays)。awk 中的数组不必提前声明,也不必声明大小。数组元素用0或空字符串来初始化,这根据上下文而定。
array[index-expression]
(1)可使用任意字符串;字符串要使用双引号括起来
(2)如果某数组元素事先不存在,在引用时,awk 会自动创建此元素,并将其值初始化为“空串”
(3)若要判断数组中是否存在某元素,要使用“index in array”格式进行遍历
(4)若要遍历数组中的每个元素,要使用for 循环**:for(var in array)** {for-body}
数字做数组索引(下标):
Array[1]="sun"
Array[2]="kai"
字符串做数组索引(下标):
Array["first"]="www"
Array"[last"]="name"
Array["birth"]="1987"
(1)数组的基本使用示例
[root@along ~]# cat awkdemo2
aaa
bbbb
aaa
123
123
123
---去除重复的行
[root@along ~]# awk '!arr[$0]++' awkdemo2
aaa
bbbb
123
---打印文件内容,和该行重复第几次出现
[root@along ~]# awk '{!arr[$0]++;print $0,arr[$0]}' awkdemo2
aaa 1
bbbb 1
aaa 2
123 1
123 2
123 3
分析:把每行作为下标,第一次进来,相当于print ias…一样结果为空,打印空,!取反结果为1,打印本行,并且++变为不空,下次进来相同的行就是相同的下标,本来上次的值,!取反为空,不打印,++变为不空,所以每次重复进来的行都不打印。
(2)数组遍历
awk 关联数组 key=>value 无序
[root@along ~]# awk 'BEGIN{abc["ceo"]="along";abc["coo"]="mayun";abc["cto"]="mahuateng";for(i in abc){print i,abc[i]}}'
coo mayun
ceo along
cto mahuateng
[root@along ~]# awk '{for(i=1;i<=NF;i++)abc[$i]++}END{for(j in abc)print j,abc[j]}' awkdemo2
aaa 2
bbbb 1
123 3
演示:
[root@along ~]# awk 'BEGIN{print rand()}'
0.237788
[root@along ~]# awk 'BEGIN{srand(); print rand()}'
0.51692
[root@along ~]# awk 'BEGIN{srand(); print rand()}'
0.189917
---取0-50随机数
[root@along ~]# awk 'BEGIN{srand(); print int(rand()*100%50)+1}'
12
[root@along ~]# awk 'BEGIN{srand(); print int(rand()*100%50)+1}'
24
演示:
[root@along ~]# echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
2008-08:08 08:08:08
[root@along ~]# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
2008-08-08 08-08-08
[root@along ~]# echo "2008:08:08 08:08:08" | awk '{split($0,i,":")}END{for(n in i){print n,i[n]}}'
4 08
5 08
1 2008
2 08
3 08 08
(1)awk脚本编写
将awk 程序写成脚本,直接调用或执行
示例:
[root@along ~]# cat f1.awk
{if($3>=1000)print $1,$3}
[root@along ~]# cat f2.awk
#!/bin/awk -f
{if($3 >= 1000)print $1,$3}
[root@along ~]# chmod +x f2.awk
[root@along ~]# ./f2.awk -F: /etc/passwd
along 1000
(2)向awk脚本传递参数
格式:
awkfile var=value var2=value2... Inputfile
注意 :在BEGIN 过程 中不可用。直到 首行输入完成以后,变量才可用 。可以通过**-v 参数**,让awk 在执行BEGIN 之前得到变量的值。命令行中每一个指定的变量都需要一个-v。
示例:
[root@along ~]# cat test.awk
#!/bin/awk -f
{if($3 >=min && $3<=max)print $1,$3}
[root@along ~]# chmod +x test.awk
[root@along ~]# ./test.awk -F: min=100 max=200 /etc/passwd
systemd-network 192
筛选给定时间范围内的日志
在/var/log/
的路径下复制某个日志文件,编写脚本文件来对所需要的时间范围内的日志进行指定筛选。
[root@localhost /]# cat time.log #拷贝的日志文件的内容
[2023-07-30T17:59:05+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-30T17:59:06+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-30T17:59:06+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:10:29+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:21:57+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:22:02+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:22:03+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:26:35+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:26:46+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:26:47+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:26:47+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:30:30+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:32:07+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:32:08+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:32:08+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T18:17:11+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-01T16:07:13+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:18:14+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:37+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:52+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:53+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:53+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:54+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:48:37+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:20+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:21+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:22+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:20+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:32:12+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:31+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:33+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:33+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T11:12:38+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T14:22:38+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-05T23:57:34+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-05T23:57:48+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-05T23:58:40+0800] INFO === Started libdnf-0.63.0 ===
awk中提供了mkrime()函数,可以将时间转化为epoch值。
[root@localhost /]# awk 'BEGIN{print mktime("2023 08 01 03 42 40")}'
1690832560
#将2023-08-01 03:42:40的时间转化为时间戳,便于进行比较
编写脚本文件
BEGIN{
#创建需要的比较的基准epoch值,用于筛选
which_time1 = mktime("2023 08 02 00 00 00")
which_time2 = mktime("2023 08 04 00 00 00")
}
{
#取出日志中的时间字符串
match($0,"^.*\\[(.*)\\].*",arr)
#调用创建的strptime1函数将摘取出来的字符串转化为epoch值
tmp_time = strptime1(arr[1])
#将所摘取日志的时间戳与基准时间戳进行比较,符合指定范围内的内容摘取出来
if(tmp_time > which_time1 && tmp_time < which_time2){print}
}
#创建将摘取时间进行重构的函数,返回时间戳
function strptime1(str,arr,Y,M,D,H,m,S){
#patsplit来取时间中的数字
patsplit(str,arr,"[0-9]{1,4}")
Y=arr[1]
M=arr[2]
D=arr[3]
H=arr[4]
m=arr[5]
S=arr[6]
return mktime(sprintf("%s %s %s %s %s %s",Y,M,D,H,m,S))
}
调用脚本文件对所保存的time.log文件中所需要的内容进行定位筛选。
[root@localhost /]# awk -f awkdemo.awk time.log
[2023-08-02T01:18:14+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:37+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:52+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:53+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:53+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:54+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:48:37+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:20+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:21+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:22+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:20+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:32:12+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:31+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:33+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:33+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T11:12:38+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T14:22:38+0800] INFO === Started libdnf-0.63.0 ===