文本处理之awk

基本语法

awk通过FS作为每一段文本的分割符(默认空格),在命令行上可以用-F参数指定分隔符;

通过RS参数指定文本换行符(默认回车,所以是一行行取数据的),通过换行符作为分割来读取文件。这里区别于sedsed是一行一行读取文件的

常用内置变量:

​ $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}

  1. 产生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是双数
  1. 如果只需要一个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

  1. 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
  1. 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, 文件中有多少行就输出多少行0

    awk {"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来表示最后一列

过滤记录

  1. 过滤第三列的值为0 && 第6列的值为LISTEN
$ awk '{print $3==0,$6}' netstat.txt

其中的==为比较运算符。其他比较运算符:!=, <, < >=

  1. 过滤第三列的值为0, 并且打印对应的第6列的信息
$ awk '{print $3==0,$6}' netstat.txt
  1. 打印第三列的值大于0的第一列和第5列的信息
$ awk '$3>0{print $1,$5}' netstat.txt
  1. 打印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)表示的是,一条记录的字段的数目.

字符串匹配

~ 表示模式开始 | / /是模糊搜索的模式 | !~ 表示模式取反

  1. 匹配第6列带有FIN的记录
$  awk '$6~/FIN/{print NR,$4,$5,$6}' netstat.txt
  1. 匹配第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
  1. 匹配第6行不带FIN的记录
$  awk '$6~/FIN/ {print NR,$4,$5,$6}' netstat.txt
  1. 匹配最后一列是/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
  1. 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
posted @ 2019-09-29 17:00  cjw1219  阅读(217)  评论(0编辑  收藏  举报