Linux三剑客之一awk

1. 使用方法

// 命令格式
awk [options] 'pattern{action}' filenames
// 脚本格式
awk -f script_file filenames

1.1 命令格式

pattern表示 AWK 在数据中查找的内容,可以用正则表达式来进行匹配,用/斜杠括起来。
action是在找到匹配内容后执行的一系列命令。{}不需要在程序中始终出现,但它们用于根据特定的模式对指令进行分组。

1.2 行处理方式

awk默认是以行为单位处理文本的,对filenames每一行都执行{}中的语句。
记录:默认为文本中的每一行
字段:默认每个记录中由空格或TAB分隔的字符串
$0表示一条记录,即文本中的一行
$1表示记录中的第一个字段

下面通过解释一个简单的命令,来了解以下工作原理:

➜  ~ awk '{print $0}' /etc/passwd              
root:x:0:0::/root:/bin/bash
nobody:x:65534:65534:Nobody:/:/sbin/nologin
dbus:x:81:81:System Message Bus:/:/sbin/nologin
bin:x:1:1::/:/sbin/nologin
daemon:x:2:2::/:/sbin/nologin
mail:x:8:12::/var/spool/mail:/sbin/nologin
ftp:x:14:11::/srv/ftp:/sbin/nologin
http:x:33:33::/srv/http:/sbin/nologin
...

'{print $0}:awk对/etc/passwd的每一行都执行了print命令。
$0:上面说了$0代表一条记录,这里默认是/etc/passwd的每一行。

下面对一个复杂一点的命令进行解释,来直观的感受一下pattern字段分隔符的效果:

➜  ~ awk -F ":" '/^system.*$/{print $1}' /etc/passwd 
systemd-journal-remote
systemd-network
systemd-resolve
systemd-timesync
systemd-coredump

-F ":":指定分隔符为:。上面说过,默认记录的分割符是空格或者TAB。
/^system.*$/:正则表达式匹配system开头的记录。
{print $1}:输出每条记录的第一个字段。

上图解释一下:


image.png

2. BEGIN和END模块

2.1 BEGIN

awk在开始处理输入文件之前会执行BEGIN模块,所以它是初始化FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。

2.2 END

awk在处理完输入文件之后会执行END模块,通常,END块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。

2.3 使用

统计/etc/passwd中帐号人数

➜  ~ awk 'BEGIN {count=0;print "[start] user count is", count} {count++;print $0} END {print "[end] user count is", count}' /etc/passwd
[start] user count is 0
root:x:0:0::/root:/bin/bash
nobody:x:65534:65534:Nobody:/:/sbin/nologin
dbus:x:81:81:System Message Bus:/:/sbin/nologin
...
[end] user count is 33

3. 内置变量

变量 含义
$0 整行记录
$1~$n 当前记录的第几个字段
FS 字段分隔符,默认是空格
RS 记录分隔符,默认是换行符
NF 当前行字段数目,就是有多少列
NR 已经读出的记录数,就是行号
OFS 输出字段分隔符,默认也是空格
ORS 输出记录分隔符,默认也是换行符

3.1 字段分隔符FS

  • FS="\t+"一个或多个TAB分隔
    ➜  ~ cat test
    awk     hello                           world!
    ➜  ~ awk 'BEGIN{FS="\t+"}{print $1,$2,$3}' test
    awk hello world!
    
  • FS="[[:space:]+]"一个或多个空格分隔(默认)
    ➜  ~ cat test
    awk hello      world!
    ➜  ~ awk 'BEGIN{FS="[[:space:]+]"}{print $1,$2,$3}' test      
    awk hello 
    ➜  ~ awk -F [[:space:]+] '{print $1,$2,$3}' test 
    awk hello
    
  • FS="[ :]+"一个或多个空格或者:分隔
    ➜  ~ cat test
    awk:hello world!
    ➜  ~ awk -F '[ :]+' '{print $1,$2,$3}' test
    awk hello world!
    ➜  ~ awk 'BEGIN {FS="[ :]+"} {print $1,$2,$3}' test           
    awk hello world!
    

3.2 字段数NF

输出字段数为2的记录

➜  ~ cat test
awk:hello:world
hell:world
➜  ~ awk -F ':' '{if(NF==2) print $0}' test
hell:world

3.3 记录数NR

输出第一条记录

➜  ~ cat test
awk:hello:world
hell:world
➜  ~ awk '{if(NR==1) print $0}' test
awk:hello:world

3.4 输出字段分隔符OFS

➜  ~ awk 'BEGIN {FS=":";OFS="QAQ"} {print $1,$2,$3}' test  
awkQAQhelloQAQworld
hellQAQworldQAQ

3.5 输出行分隔符ORS

➜  ~ awk 'BEGIN {FS=":";ORS="\nQAQ\n"} {print $0}' test 
awk:hello:world
QAQ
hell:world
QAQ

4. awk的if、循环和数组

4.1 条件语句

上面已经给了很多使用实例了,这里就不多说了

4.2 循环结构

  • while循环
    ➜  ~ vim script
    #!/bin/awk
    BEGIN {
        i=1
    }
    {
        while (i < 3) {
            print i
            i++
        }
        print $0
    }
    ➜  ~ awk -f script test
    1
    2
    awk:hello:world
    hell:world
    
  • for循环
    ➜  ~ vim script 
    #!/bin/awk
    {
            for (i=1;i<4;i++) {
                    print i
            }
    }
    ➜  ~ cat test
    awk:hello:world
    hell:world
    ➜  ~ awk -f script test
    1
    2
    3
    1
    2
    3
    # 因为test有两行,每一行执行一次script,所有输出了两次1 2 3
    

4.3 数组

awk中的数组都是关联数组,数字索引也会转变为字符串索引

➜  ~ vim script        
#!/bin/awk
{ 
        cities[1]="beijing"
        cities[2]="shanghai"
        cities["three"]="guangzhou"
        for( c in cities) {
                print cities[c]
        }
        print "---"
        print cities[1]
        print cities["1"]
        print cities["three"]
}
➜  ~ cat test
awk:hello:world
➜  ~ awk -f script test
guangzhou
beijing
shanghai
---
beijing
beijing
guangzhou

查看网络汇总状态

➜  ~ netstat -an | awk '/^tcp/ {s[$NF]++} END {for (i in s) print i,s[i]}'
LISTEN 20
ESTABLISHED 5

5. 案例

5.1 两个文件求交集

➜  ~ cat a
1,name
2,name
3,name
4,name
5,name
6,name
➜  ~ cat b
3,city
6,city
7,city
➜  ~ vim script                       
#!/bin/awk
BEGIN {
        FS=","
}
{
        if (NR==FNR) {
                a[$1]++
        }
        if (NR>FNR) {
                if ($1 in a) {
                        print $0
                }
        }
}
➜  ~ awk -f script a b                
3,city
6,city

脚本解释:
BEGIN{FS=","}:很简单了,指定分隔符。
if (NR==FNR) {a[$1]++}:这里是把第一个文件的第一列作为数组的索引,将它出现的次数作为值。
if (NR>FNR) {if ($1 in a) {print $0}}:这里是查看第二个文件当前第一列是否在上面的数组中,如果存在则输出。
重点解释:FNR
NR都已经明白是awk已经读出的记录数,就是行号。
FNR,与NR类似,但是不同的是每当awk打开一个新文件时,FNR便重新计数;而NR是继续计数。
举例:

  • 只打开一个文件时,两者没有什么区别
    ➜  ~ awk '{print NR,$0}' t1   
    1 a
    2 b
    3 c
    4 d
    5 e
    6 f
    ➜  ~ awk '{print FNR,$0}' t1 
    1 a
    2 b
    3 c
    4 d
    5 e
    6 f
    
  • 但是当打开多个文件呢?
    ➜  ~ awk '{print NR,$0}' t1 t2 
    1 a
    2 b
    3 c
    4 d
    5 e
    6 f
    7 a
    8 b
    9 c
    ➜  ~ awk '{print FNR,$0}' t1 t2 
    1 a
    2 b
    3 c
    4 d
    5 e
    6 f
    # 这里打开t2文件时,行号重新计数了
    1 a
    2 b
    3 c
    

你可能感兴趣的:(Linux三剑客之一awk)