文本处理之awk
基本语法
awk
通过FS作为每一段文本的分割符(默认空格),在命令行上可以用-F参数指定分隔符;
通过RS参数指定文本换行符(默认回车,所以是一行行取数据的),通过换行符作为分割来读取文件。这里区别于sed
,sed
是一行一行读取文件的
常用内置变量:
$0 当前所有字段
$1--$n 按照分隔符分割取到的第n列内容
FS 分隔符(默认空格) awk 'BEGIN{FS=":"}{print $1}' /etc/passwd ;等价于awk -F: '{print $1}' /etc/passwd
RS 换行符(默认回车) awk 'BEGIN{RS=":"}{print $1}' /etc/passwd
NF 字符列数,当前处理行的分割后的列数 awk -F: '{print NF}' /etc/passwd
NR 行号 awk -F: '{print NR ":" $1}' /etc/passwd
OFS (默认空格)输出字段分隔符
ORS (默认回车)输出记录分隔符
自定义外部变量
-v:自定义变量
awk -v host=$HOSTNAME "BEGIN{print host}"
关系操作符
<、>、<=、>=、==、!=、、!
~
:用来判断前面的列是否匹配后面的内容。例如awk -F: '$7 ~ /^\/bin/{print $0}' /etc/passwd
(判断第7列是否以/bin开头,如果是打印该列)
!~
:不匹配
输出
print与printf
print:直接输出
$ awk -F: '{print $1 ":" $2}' /etc/passwd
printf:格式化输出
$ awk -F: '{printf "hello %s, your path is %s\n",$1,$NF}' /etc/passwd
hello root, your path is /bin/bash
hello bin, your path is /sbin/nologin
hello daemon, your path is /sbin/nologin
hello adm, your path is /sbin/nologin
hello lp, your path is /sbin/nologin
....
注意:printf
需要手动增加\n来换行。使用%s
来格式化,printf
外加入要替换的变量
awk的流程控制
if语句 if(expression){action1}else{action2}
- 产生10个数seq 10,通过if语句判断是单数还是双数
$ seq 10 |awk '{if($0%2==0){print $0"是双数"}else{print $0"是单数"}}'
1是单数
2是双数
3是单数
4是双数
5是单数
6是双数
7是单数
8是双数
9是单数
10是双数
- 如果只需要一个if分支,可以省略前面的if
$ awk -F: '$3<10{printf"用户名 -> %s %s uid --> %s\n", $1,"-->",$3}' /etc/passwd
用户名 -> root --> uid --> 0
用户名 -> bin --> uid --> 1
用户名 -> daemon --> uid --> 2
用户名 -> adm --> uid --> 3
用户名 -> lp --> uid --> 4
用户名 -> sync --> uid --> 5
用户名 -> shutdown --> uid --> 6
用户名 -> halt --> uid --> 7
用户名 -> mail --> uid --> 8
$ awk -F: '{if($3<10)printf"用户名 -> %s %s uid --> %s\n", $1,"-->",$3}' /etc/passwd
用户名 -> root --> uid --> 0
用户名 -> bin --> uid --> 1
用户名 -> daemon --> uid --> 2
用户名 -> adm --> uid --> 3
用户名 -> lp --> uid --> 4
用户名 -> sync --> uid --> 5
用户名 -> shutdown --> uid --> 6
用户名 -> halt --> uid --> 7
用户名 -> mail --> uid --> 8
循环
while
while(expression){action}
例子:使用:分割/etc/passwd
,并将每一列前加上列号
$ awk -F: '{i=1;while(i<=NF){print i":"$i;i++}}' /etc/passwd
1:root
2:x
3:0
4:0
5:root
6:/root
7:/bin/bash
....
设置变量i
初始值为1, while
循环每次循环自增1, 设定条件i
的值永远小于字段数, awk
首先会读一行, 然后会以:
为分隔符读取每一列数据, NF表示字段的总数, 最后打印i
和$i
(i
表示循环每一列的列好, $i
表示每一列的值)
for
for(i=0;i<=10;i++){action}
分割/etc/passwd
,并将每一列前加上列号
$ awk -F: '{for(i=1;i<=NF;i++){print i":"$i}}' /etc/passwd
1:root
2:x
3:0
4:0
5:root
6:/root
7:/bin/bash
- for(value in array)
当value在array的key中,进行下面的操作。awk
的数组类似python中的字典。
统计/etc/passwd
第7列的值及对应的个数
$ awk -F: '{a[$7]++}END{for(i in a)if(i!=""){print i":"a[i]}}' /etc/passwd
/bin/sync:1
/bin/bash:2
/sbin/nologin:16
/sbin/halt:1
/sbin/shutdown:1
创建数组a,以第7列作为下标,使用运算符++作为数组元素,元素初始值为0。处理/etc/passwd
第一行时,下标是某个路径(/bin/bash
),元素加1,处理第二行时,下标又是一个路径(/sbin/nologin
),元素加1,如果这个路径已经存在在数组a中,则a[/sbin/nologin
]的数组元素加1,也就是这个路径出现了两次,元素结果是2,以此类推。因此可以实现去重,统计出现次数。
核心语句放在END因为需要将数据收集完后才取统计这些数据
数组
array[1]="hello"
array["name"]="Jack"
数组类似python的字典,array[key值]="value值";key为索引,可以是数字也可以是字符串。
数组元素的删除:delete array["key"]
例子:定义了数组a的三个值,并打印结果查看
$ awk 'BEGIN{a[1]="hello";a[2]="word";a["name"]="meitian";for(i in a){print "key为"i":value为"a[i]}}'
key为name:value为meitian
key为1:value为hello
key为2:value为word
函数
split
把一个字符串分隔为单词并存储在数组中
格式:
split (string, array, field separator)
split (string, array) -->如果第三个参数没有提供,awk
就默认使用当前FS
值。
$ time=`date |awk '{print $5}'`
$ echo $time
20:40:15
$ echo $time|awk '{split($0,a,":");print a[1],a[2],a[3]}'
20 40 15
substr
截取字符串
返回从起始位置起,指定长度之子字符串;若未指定长度,则返回从起始位置到字符串末尾的子字符串。
格式:
substr(s,p)返回字符串s中从p开始的后缀部分
substr(s,p,n)返回字符串s中从p开始长度为n的后缀部分
$ echo "hhp love a man" |awk '{print substr($2,1,4)}'
love
$ echo "hhp love a man" |awk '{print substr($0,1,2)}'
hh
$ echo "hhp love a man" |awk '{print substr($0,1,3)}'
hhp
length
字符串长度
length函数返回没有参数的字符串的长度。length函数返回整个记录中的字符数
$ echo "hhp love a man" |awk '{print length}'
14
gsub
gsub函数则使得在所有正则表达式被匹配的时候都发生替换。
gsub(regular expression,subsitution string,target string);简称gsub (r,s,t)
把一个文件里面所有包含abc的行里面的abc替换成def,然后输出第一列和第三列
$ echo "hhp love a man" |awk '$0 ~/hhp/{gsub("hhp","hhpsb");print $0}'
hhpsb love a man
next
在awk中,如果调用next,那么next之后的命令就都在执行了,此行文本的处理到此结束,那么读取下一条记录并操作
$ cat test.txt
Tom 2012-12-11 car 53000
John 2013-01-13 bike 41000
vivi 2013-01-18 car 42800
Tom 2013-01-20 car 32500
John 2013-01-28 bike 63500
$ awk '{if(NR==1){next}print $0}' test.txt
John 2013-01-13 bike 41000
vivi 2013-01-18 car 42800
Tom 2013-01-20 car 32500
John 2013-01-28 bike 63500
基本的实例
素材准备
$ cat netstat.txt
Proto Recv-Q Send-Q Local-Address Foreign-Address State
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN
tcp 0 0 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT
tcp 0 0 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2
tcp 0 0 coolshell.cn:80 11.194.134.189:1032 ESTABLISHED
tcp 0 0 coolshell.cn:80 123.169.124.11:49809 ESTABLISHED
tcp 0 0 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2
tcp 0 0 coolshell.cn:80 12.16.124.111:49829 ESTABLISHED
tcp 0 0 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT
tcp 0 4166 coolshell.cn:80 61.148.24.38:30901 ESTABLISHED
tcp 0 1 coolshell.cn:80 12.152.181.209:26825 FIN_WAIT1
tcp 0 0 coolshell.cn:80 110.194.134.189:47 ESTABLISHED
输出第1列和第4例
$ awk {'print $1,$3'} netstat.txt
其中单引号中的被大括号括着的就是
awk
的语句,注意,其只能被单引号包含
其中的$1..$n
表示第几例。注:$0
表示整个行
补充
如果换成双引号, awk
语法就不起作用了
-
输入
$1-$9
,都会显示文件中的所有内容$ awk {"print $1"} netstat.txt Proto Recv-Q Send-Q Local-Address Foreign-Address State tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN tcp 0 0 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT tcp 0 0 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2 tcp 0 0 coolshell.cn:80 11.194.134.189:1032 ESTABLISHED tcp 0 0 coolshell.cn:80 123.169.124.11:49809 ESTABLISHED tcp 0 0 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2 tcp 0 0 coolshell.cn:80 12.16.124.111:49829 ESTABLISHED tcp 0 0 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT tcp 0 4166 coolshell.cn:80 61.148.24.38:30901 ESTABLISHED tcp 0 1 coolshell.cn:80 12.152.181.209:26825 FIN_WAIT1 tcp 0 0 coolshell.cn:80 110.194.134.189:47 ESTABLISHED
-
输入
$0
就会输出0
, 文件中有多少行就输出多少行0awk {"print $0"} netstat.txt 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-
输入
$10
以后的数字就会输出的值和echo $10
是一样的, 文件中有多少行就输出多少行0$ awk {"print $10"} netstat.txt 0 0 0 0 0 0 0 0 0 0 0 0 0 0 $ echo $10 0
-
echo $10
规律会首先输出echo $1, 然后在输出后面的数字
-
echo $01
规律:会首先输出echo $0, 然后在输出后面的数字
$ echo $10 0 $ echo $11 1 $ echo $19 9 $ echo $190 90 $ echo $01 -bash1 $ echo $1 $ echo $01ssdsd -bash1ssdsd
-
显示第一列和最后一列
$ awk '{print $1,"-->",$NF}' netstat.txt
这里$NF
来表示最后一列
过滤记录
- 过滤第三列的值为
0
&& 第6列的值为LISTEN
$ awk '{print $3==0,$6}' netstat.txt
其中的==
为比较运算符。其他比较运算符:!=
, <
, <
>=
- 过滤第三列的值为
0
, 并且打印对应的第6列的信息
$ awk '{print $3==0,$6}' netstat.txt
- 打印第三列的值大于0的第一列和第5列的信息
$ awk '$3>0{print $1,$5}' netstat.txt
- 打印uid>10的用户名和uid
$ awk -F: '$3<10{printf"用户名 -> %s %s uid --> %s\n", $1,"-->",$3}' /etc/passwd
用户名 -> root --> uid --> 0
用户名 -> bin --> uid --> 1
用户名 -> daemon --> uid --> 2
用户名 -> adm --> uid --> 3
用户名 -> lp --> uid --> 4
用户名 -> sync --> uid --> 5
用户名 -> shutdown --> uid --> 6
用户名 -> halt --> uid --> 7
用户名 -> mail --> uid --> 8
$ awk -F: '{if($3<10)printf"用户名 -> %s %s uid --> %s\n", $1,"-->",$3}' /etc/passwd
用户名 -> root --> uid --> 0
用户名 -> bin --> uid --> 1
用户名 -> daemon --> uid --> 2
用户名 -> adm --> uid --> 3
用户名 -> lp --> uid --> 4
用户名 -> sync --> uid --> 5
用户名 -> shutdown --> uid --> 6
用户名 -> halt --> uid --> 7
用户名 -> mail --> uid --> 8
指定分隔符
$ awk -F: '{print $1,$6,$7}' /etc/passwd # 输出用户名,家目录,解释器
-F
的意思就是指定分隔符
注:如果你要指定多个分隔符,你可以这样来:
awk -F '[;:]'
以\t
作为分隔符输出
$ awk -F: '{print $1, $6, $7}' OFS='\t' /etc/passwd # 输出用户名,家目录,解释器, 三列数据之间使用\t相隔
OFS
表示的是分隔符
内建变量
awk '{print NR, "->", $1, $6, $7, "字段数","=>",NF}' OFS='\t' netstat.txt
NR(Number of Record)
表示的是已经处理过的总记录数目,或者说行号(不一定是一个文件,可能是多个)
NF(Number for Field)
表示的是,一条记录的字段的数目.
字符串匹配
~
表示模式开始 |/ /
是模糊搜索的模式 |!~
表示模式取反
- 匹配第6列带有FIN的记录
$ awk '$6~/FIN/{print NR,$4,$5,$6}' netstat.txt
- 匹配第6列带有FIN和TIME的记录
$ awk '$6~/FIN|TIME/ {print NR,$4,$5,$6}' netstat.txt
5 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT
6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2
9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2
11 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT
13 coolshell.cn:80 12.152.181.209:26825 FIN_WAIT1
- 匹配第6行不带FIN的记录
$ awk '$6~/FIN/ {print NR,$4,$5,$6}' netstat.txt
- 匹配最后一列是
/sbin/nologin
的数据
$ awk '$NF~/\/bin\/bash/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
java:x:1000:1000:java:/home/java:/bin/bash
- 像
grep
一样的去匹配
$ grep 'LISTEN' netstat.txt
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN
$ awk '/LISTEN/' netstat.txt
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN
$ awk '/LIST/ {print NR,$0}' netstat.txt
2 tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
3 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
4 tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN
$ grep -n 'LIST' netstat.txt
2:tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
3:tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
4:tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN
拆分文件
将
awk
输出的内容重定向到一个文件
$ awk 'NR!=1{print $6}' netstat.txt > col6.txt
$ cat col6.txt
统计
统计/etc/passwd
第7列的值及对应的个数
$ awk -F: '{a[$7]++}END{for(i in a)if(i!=""){print i":"a[i]}}' /etc/passwd
/bin/sync:1
/bin/bash:2
/sbin/nologin:16
/sbin/halt:1
/sbin/shutdown:1
创建数组a,以第7列作为下标,使用运算符++作为数组元素,元素初始值为0。处理/etc/passwd
第一行时,下标是某个路径(/bin/bash
),元素加1,处理第二行时,下标又是一个路径(/sbin/nologin
),元素加1,如果这个路径已经存在在数组a中,则a[/sbin/nologin
]的数组元素加1,也就是这个路径出现了两次,元素结果是2,以此类推。因此可以实现去重,统计出现次数。
核心语句放在END因为需要将数据收集完后才取统计这些数据
统计各个connection
$ awk 'NR!=1{a[$6]++;} END {for (i in a) print i ", " a[i];}' netstat.txt
LISTEN, 3
ESTABLISHED, 5
FIN_WAIT1, 1
FIN_WAIT2, 2
TIME_WAIT, 2
统计每个用户的进程的占了多少内存
$ ps aux | awk 'NR!=1{a[$1]+=$6;} END { for(i in a) print i ", " a[i]"KB";}'
chrony, 2084KB
dbus, 2596KB
polkitd, 13968KB
postfix, 8156KB
root, 127468KB
创建数组a,以第1列作为下标,使用运算符+=$6作为数组元素,元素初始值为0。处理ps aux
第一行时,下标是某个用户, 值是这个用户所在行的第六列, 元素的值加上第六列的值, 这样以此类推, 可以将每个用户所起的进程的总内存进行统计
核心语句放在END因为需要将数据收集完后才取统计这些数据
ps aux每一列参数解释
[root@git ~]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.6 127968 6552 ? Ss 09:23 0:01 /usr/lib/systemd/systemd --switched-root --system --deseri
root 2 0.0 0.0 0 0 ? S 09:23 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S 09:23 0:00 [ksoftirqd/0]
root 5 0.0 0.0 0 0 ? S< 09:23 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? S 09:23 0:00 [kworker/u256:0]
root 7 0.0 0.0 0 0 ? S 09:23 0:00 [migration/0]
root 8 0.0 0.0 0 0 ? S 09:23 0:00 [rcu_bh]
root 9 0.0 0.0 0 0 ? R 09:23 0:00 [rcu_sched]
root 10 0.0 0.0 0 0 ? S< 09:23 0:00 [lru-add-drain]
...
USER: 运行进程的用户
PID 进程的ID号
%CPU 进程占用CPU的百分比
%MEM 进程占用内存的百分比
VSZ 进程占用虚拟内存的百分比
RSS 占用固定内存的百分比
TTY 进程在哪个终端运行, 如果和终端无关, 会显示?; 如果显示pts/0, 则表示通过网络连接主机并运行进程
STAT 进程的运行状态, linux下一共有5种状态
- D 不可中断 uninterruptible sleep (usually IO)
- R 运行 runnable (on run queue)
- S 中断 sleeping
- T 停止 traced or stopped
- Z 僵尸 a defunct (”zombie”) process
START 进程的开始时间
TIME 进程的执行时间
COMMAND 进程的执行路径
函数实例
素材准备
Tom 2012-12-11 car 53000
John 2013-01-13 bike 41000
vivi 2013-01-18 car 42800
Tom 2013-01-20 car 32500
John 2013-01-28 bike 63500
split
输出一月份每个员工的工资:
$ awk '{split($2,a,"-");if(a[2]=01){sum[$1]+=$4}}END{for(i in sum) print sum[i],i}' test.txt
42800 vivi
85500 Tom
104500 John
gsub
将/sbin/nologin
替换成/bin/bash
$ cp /etc/passwd passwd
$ awk -F: '$NF~/\/sbin\/nologin/{print $1,"-->",$NF}' passwd
bin --> /sbin/nologin
daemon --> /sbin/nologin
adm --> /sbin/nologin
lp --> /sbin/nologin
mail --> /sbin/nologin
operator --> /sbin/nologin
games --> /sbin/nologin
ftp --> /sbin/nologin
nobody --> /sbin/nologin
systemd-network --> /sbin/nologin
dbus --> /sbin/nologin
polkitd --> /sbin/nologin
tss --> /sbin/nologin
sshd --> /sbin/nologin
postfix --> /sbin/nologin
chrony --> /sbin/nologin
$ awk -F: '{gsub("/sbin/nologin","/bin/bash")}$NF~/\/bin\/bash/{print $1,"-->",$NF}' passwd
root --> /bin/bash
bin --> /bin/bash
daemon --> /bin/bash
adm --> /bin/bash
lp --> /bin/bash
mail --> /bin/bash
operator --> /bin/bash
games --> /bin/bash
ftp --> /bin/bash
nobody --> /bin/bash
systemd-network --> /bin/bash
dbus --> /bin/bash
polkitd --> /bin/bash
tss --> /bin/bash
sshd --> /bin/bash
postfix --> /bin/bash
chrony --> /bin/bash
java --> /bin/bash
核心代码段是 {gsub("/sbin/nologin","/bin/bash")}
, 将passwd文件中每一行中的 /sbin/nologin
替换成 /bin/bash
$NF~/\/bin\/bash/{print $1,"-->",$NF}
这段代码就是将处理好的文件进行过滤, 将文件中/bin/bash
的字符串匹配出来, 并输出第一列和最后一列
substr
$ echo "hhp love a man" |awk '{print substr($2,1,4)}'
love
$ echo "hhp love a man" |awk '{print substr($0,1,2)}'
hh
$ echo "hhp love a man" |awk '{print substr($0,1,3)}'
hhp
length
$ echo "hhp love a man" |awk '{print length}'
14
next
$ cat test.txt
Tom 2012-12-11 car 53000
John 2013-01-13 bike 41000
vivi 2013-01-18 car 42800
Tom 2013-01-20 car 32500
John 2013-01-28 bike 63500
$ awk '{if(NR==1){next}print $0}' test.txt
John 2013-01-13 bike 41000
vivi 2013-01-18 car 42800
Tom 2013-01-20 car 32500
John 2013-01-28 bike 63500