1.awk命令详解

awk是一个强大的文本处理工具,用于格式化输出文本,善于对列进行操作。而sed命令擅长对行进行操作。

和 sed 命令类似,awk 命令也是逐行扫描文件(从第 1 行到最后一行),寻找含有目标文本的行,如果匹配成功,则会在该行上执行用户想要的操作;反之,则不对行做任何处理。

awk命令的语法如下:

awk [options] ‘program’ var=value file …
awk [options] –f programfile var=value file …
awk [optonns] ‘BEGIN{action;…} pattern{action;…} END {action;…}’ file…

其中program由pattern{action;…}两部分组成。

  • pattern代表匹配规则,指定action可以作用到文本内容中的具体范围或action的执行条件,可以使用字符串或者正则表达式或者逻辑判断等条件。如果不指定匹配规则,则默认匹配文本中所有的行;

  • action则为执行的操作,多个操作之间使用分号";"连接,需要使用大括号{}括起来,如果没有指定执行命令,则默认打印本行;

  • BEGIN:表示在处理文本第一行前需要预先执行的操作,只执行一次,例如打印表头;

  • END:表示在处理完文本的最后一行后需要执行的动作,只执行一次,例如执行统计动作;

  • awk的常见选项如下:

    选项说明
    -F fs指定以 fs 作为输入一行中域的分隔符,awk 命令默认分隔符为空格或制表符。
    -f file从脚本文件中读取 awk 脚本指令,以取代直接在命令行中输入指令。
    -v var=val在执行处理过程之前,设置一个变量 var,并给其设备初始值为 val。
  • record:记录,在AWK中行称为记录,默认的记录分隔符为换行\n;

  • field:域,awl中列称为字段或域,默认的列分隔符为空格或tab,支持自动识别多个空格或tab为一个分隔符;

1.1 awk的处理流程

awk的处理流程图如下:

linux文本三剑客之awk详解_awk

awk的处理流程为:

  1. 通过关键字 BEGIN 执行 BEGIN 块的内容,即 BEGIN 后花括号 {} 的内容。
  2. 完成 BEGIN 块的执行,开始执行body块。
  3. 依次读入有 \n 换行符分割的记录。
  4. 将记录按指定的域分隔符划分域,填充域,并未每个域使用变量表示,$0 则表示所有域(即本行内容),\(1 表示第一个域,\)n 表示第 n 个域。
  5. 依次执行各 BODY 块,pattern 部分匹配该行内容成功后,才会执行 awk-commands 的内容。
  6. 循环读取并执行各行直到文件结束,完成body块执行。
  7. 开始 END 块执行,END 块可以输出最终结果。

1.2 awk中的变量

awk中可以使用变量来存取值,分为内置变量和自定义变量。

1.2.1 内置变量

常用内置变量如下:

  • $0:代表整个文本行;

    [root@xuzhichao ~]# awk -F: '/^\/{print $0}' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
  • $n:代表一行中的第 n 个数据字段;

    #打印root行的用户名和UID
    [root@xuzhichao ~]# awk -F: '/^\/{print $1,$3}' /etc/passwd
    root 0
  • FS:输入字段分隔符,默认为空格或tab;

  • OFS:输出字段分隔符;

    #指定输入和输出的分隔符都为分号:print 命令会自动将 OFS 变量的值放置在输出中的每个字段间。通过设置 OFS 变量,可以在输出中使用任意字符串来分隔字段。
    [root@xuzhichao ~]# awk -v FS=: -v OFS=: '/^\/{print $1,$3}' /etc/passwd
    root:0
  • RS:指定输入记录换行符,默认是回车

  • ORS :输出的换行符,默认是回车

    #对于如下文件,前三行代表一个人的信息,后三行代表另一个人的信息,需要把每个人的信息作文一个整体进行处理,使用默认的RS无法实现,此时需要把RS设置为空行“”,然后把NS设置为回车\n,则能把每个人的信息作为一个整体,姓名,电话,地址分别作为一个字段处理。
    [root@xuzhichao ~]# cat f1
    xiaoming
    17345332343
    zhengzhou
    
    xiaohong
    13459872309
    beijing
    [root@xuzhichao ~]# awk -v FS="\n" -v RS="" '{print $1,$3}' f1
    xiaoming zhengzhou
    xiaohong beijing
    
    #把RS设置为;把ORS设置为=
    [root@xuzhichao ~]# awk -v RS=";" -v ORS="=" '{print $1}'
    1;2;3;4;   
    1=2=3=4=
  • NF:一行的字段的数量

    #显示/etc/passwd文件每行有多少个字段
    [root@xuzhichao ~]# head -n 3 /etc/passwd |awk -F: '{print NF}'
    7
    7
    7
    
    #显示/etc/passwd文件的最后一列
    [root@xuzhichao ~]# head -n 3 /etc/passwd |awk -F: '{print $NF}'
    /bin/bash
    /sbin/nologin
    /sbin/nologin
    
    #显示/etc/passwd文件的倒数第二列
    [root@xuzhichao ~]# head -n 3 /etc/passwd |awk -F: '{print $(NF-1)}'
    /root
    /bin
    /sbin
  • NR:文件的行号,记录号

    #显示行号和USER
    [root@xuzhichao ~]# head -n 3 /etc/passwd |awk -F: '{print NR,$1}'
    1 root
    2 bin
    3 daemon
  • FNR:与NR一样,都是文件的行号,区别在于当处理多个文件时,NR会对这些文件统一编行号,FNR则会针对每个文件单独编行号

    [root@xuzhichao ~]# head -n 3 /etc/passwd > f1
    [root@xuzhichao ~]# tail -n 3 /etc/passwd > f2
    [root@xuzhichao ~]# cat f1
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    [root@xuzhichao ~]# cat f2
    apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
    admroot:x:1001:1001::/home/admroot:/bin/bash
    rooter:x:1002:1002::/home/rooter:/bin/bash
    #NR对f1和f2两个文件统一编写行号
    [root@xuzhichao ~]# awk -F: '{print NR,$1}' f1 f2   
    1 root
    2 bin
    3 daemon
    4 apache
    5 admroot
    6 rooter
    #FNR会对f1和f2两个文件单独编写行号
    [root@xuzhichao ~]# awk -F: '{print FNR,$1}' f1 f2
    1 root
    2 bin
    3 daemon
    1 apache
    2 admroot
    3 rooter
  • FILENAME:处理的文件名

    [root@xuzhichao ~]# awk -F: '{print FNR,FILENAME,$1}' f1 f2
    1 f1 root
    2 f1 bin
    3 f1 daemon
    1 f2 apache
    2 f2 admroot
    3 f2 rooter
  • ARGC:awk命令中命令行参数个数

  • ARGV:表示一个数组,存放的是awk命令中命令行所有的参数,ARGV[0]代表第一个参数,ARGV[1]代表第二个参数

    #以下示例中表示以下awk命令中共有三个参数,第一个参数时awk命令自身,第二个参数时f1,第三个参数是f2
    [root@xuzhichao ~]# awk -F: 'BEGIN{print ARGC,ARGV[0],ARGV[1],ARGV[2]}' f1 f2
    3 awk f1 f2

1.2.2 自定义变量

awk中还可以自定义变量,可以定义的地方为:

  1. 使用-v参数在awk选项中定义变量;
  2. 在action中直接定义变量,例如var="name";

调用变量时直接调用变量名字即可,不需要像bash中使用$符号。

#使用两种种方法定义变量
[root@xuzhichao ~]# head -n 3 /etc/passwd | awk -v FS=: -v var="name" '{print var,$1}'
name root
name bin
name daemon
[root@xuzhichao ~]# head -n 3 /etc/passwd | awk -F: '{var="name";print var,$1}'
name root
name bin
name daemon

1.3 awk脚本

awk 允许将脚本命令存储到文件中,然后再在命令行中引用。

请看如下示例:

#示例一:
[root@xuzhichao ~]# cat awk.txt 
/^\/{print $1,$3}
[root@xuzhichao ~]# awk -F: -f awk.txt /etc/passwd
root 0

#示例二:
[root@xuzhichao ~]# cat awk.txt 
#!/bin/awk -f
/^\/{print $1,$3}
[root@xuzhichao ~]# chmod +x awk.txt 
[root@xuzhichao ~]# ./awk.txt -F: /etc/passwd
root 0

在awk程序文件中,可以指定多条命令,一条命令放一行即可,之间不需要用分号。

[root@xuzhichao ~]# cat awk.txt 
#!/bin/awk -f
BEGIN{print "user uid"}
/^\/{print $1,$3}

[root@xuzhichao ~]# ./awk.txt -F: /etc/passwd
user uid
root 0

awk脚本中可以直接引用一个变量名称,然后在awk命令中进行赋值,即时改变变量的值,例如:

[root@xuzhichao ~]# cat awk.txt
{if($3 >= min && $3 <=max)print $1,$3}

#在awk命令中对变量赋值
[root@xuzhichao ~]# awk -F: -f awk.txt min=0 max=3 /etc/passwd
root 0
bin 1
daemon 2
adm 3

注意:为脚本的变量赋值时,上面这种赋值的方法对BEGIN代码块中的变量无法赋值,如果需要对BEGIN代码块中的变量赋值。需要使用-v参数,看下面的示例:

[root@xuzhichao ~]# cat awk.txt 
BEGIN{print var,str}
{if($3 >= min && $3 <=max)print $1,$3}
#无法输出BEGIN中的变量值
[root@xuzhichao ~]# awk -F: -f awk.txt min=0 max=3 var=user str=uid /etc/passwd

root 0
bin 1
daemon 2
adm 3
#使用-v参数对变量赋值时可以对BEGIN中的变量成功赋值
[root@xuzhichao ~]# awk -F: -f awk.txt -v min=0 -v max=3 -v var=user -v str=uid /etc/passwd
user uid
root 0
bin 1
daemon 2
adm 3

1.4 printf命令:格式化输出

printf命令用于格式化输出文本,使用格式如下:

printf "FORMAT" ,item1,iterm2,iterm3,...
注意:
1."FORMAT"为必须项,需要分别为后面的iterm指定格式符;
2.iterm为变量名称;
3.不会自动换行,需要使用\n进行换行;
4."FORMAT"中普通字符串直接给出即可,无需使用引号;
  • “FORMAT”中的定义iterm的格式符如下:

    格式符意义
    %d或%i显示十进制整数
    %s显示字符串
    %f显示浮点数
    %u无符号整数
    %%显示%自身
  • 以上格式符需要结合修饰符使用,常用修饰符如下:

    修饰符意义
    #[.#]第一个#表示整个浮点数的宽度,第二个#表示小数点后的精度,例如%3.1f
    #在整数类型或字符串类型中表示整数或字符串的宽度,例如%5s
    -左对齐,默认为右对齐,例如%-5d
    +表示数值的正负号,例如%+d

printf的使用示例如下:

[root@xuzhichao ~]# head -n 3 /etc/passwd | awk -F: 'BEGIN{var="username";str="uid";printf "%10s  |  %5s\n",var,str} {printf "%10s  |  %5d\n",$1,$3}'
  username  |    uid
      root  |      0
       bin  |      1
    daemon  |      2

[root@xuzhichao ~]# head -n 3 /etc/passwd | awk -F: 'BEGIN{var="username";str="uid";printf "%-10s  |  %-5s\n",var,str} {printf "%-10s  |  %-5d\n",$1,$3}'
username    |  uid  
root        |  0    
bin         |  1    
daemon      |  2

1.5 awk的pattern部分说明

awk命令中的pattern部分用于定义匹配规则,支持的pattern形式有如下几种:

  • /regular expression/:支持扩展正则表达式,仅处理被模式匹配的行。

    #示例一:取出以UUID开头的行的第一个字段
    [root@xuzhichao ~]# awk '/^UUID/{print $1}' /etc/fstab 
    UUID=d643dd72-da0b-4f07-bae0-7f98b7e69f59
    
    #示例二:取反操作,即取出不以UUID开头的行的第一个字段
    [root@xuzhichao ~]# awk '!/^UUID/{print $1}' /etc/fstab 
    
    #示例三:去除/etc/httpd/conf/httpd.conf中所有注释行,默认action为打印本行
    [root@xuzhichao ~]# awk '!/^[[:space:]]*#/' /etc/httpd/conf/httpd.conf
  • 关系表达式:结果为真则处理后面的action,结果为假则不处理action。

    #示例一:!0为真,执行action;!1为假,不执行action
    [root@xuzhichao ~]# head -n 1 /etc/passwd | awk -F: '!0{print $1,$3}'
    root 0
    [root@xuzhichao ~]# head -n 1 /etc/passwd | awk -F: '!1{print $1,$3}'
    
    #示例二:打印奇数行和偶数行
    #默认i为0,执行i=!i后i为1,打印第一行,然后执行i=!i后i为0,不执行第二行,依次类推。
    [root@xuzhichao ~]# seq 6 |awk 'i=!i'
    1
    3
    5
    [root@xuzhichao ~]# seq 6 |awk -v i=1 'i=!i'
    2
    4
    6
    [root@xuzhichao ~]# seq 6 |awk '!(i=!i)'
    2
    4
    6
    
    #示例三:打印/etc/passwd文件中shell类型为/bin/bash的用户名和shell类型
    [root@xuzhichao ~]# awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
    root /bin/bash
    xu /bin/bash
    • 结果为真:表示结果为非0值,或非空字符串;
    • 结果为假:表示结果为0或为空字符串;
  • 匹配行范围:/pattern1/,/pattern2/,表示从pattern1匹配的将到pattern2匹配的行之间的范围,不支持像sed那样直接给出行号。

    #示例一:打印/etc/passwd文件从root用户到xu用户之间的行的用户名和UID
    [root@xuzhichao ~]# awk -F: '/^\/,/^\/{print $1,$3}' /etc/passwd
    
    #示例二:打印/etc/passwd文件5行到8行之间的行和行号
    [root@xuzhichao ~]# awk -F: '(NR>=5 && NR2000 && $32000 && $3<3000{print $1}' /etc/passwd | sed -r 's/(.*)/userdel -r \1/' | bash
    
    #示例二:输出系统中所有的普通用户
    [root@xuzhichao ~]# awk -F ':' '$3!=0{print $1}' /etc/passwd
    
    #示例三:显示uid为0的行
    [root@xuzhichao ~]# awk -F ':' '$3==0 {print $0}' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
  • 模式匹配符

    匹配符说明
    ~左边内容包含右边内容,右边内容支持正则表达式,使用//即可
    !~左边内容不包含右边内容
    #示例一:打印文件中含有root的用户
    [root@xuzhichao ~]# awk -F ':' '$0 ~ "root" {print $0}' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin
    admroot:x:1001:1001::/home/admroot:/bin/bash
    rooter:x:1002:1002::/home/rooter:/bin/bas
    
    #示例二:
    [root@xuzhichao ~]# awk -F ':' '$1 ~ /\/ {print $0}' /etc/passwd
    [root@xuzhichao ~]# awk -F ':' '$1 !~ /\/ {print $0}' /etc/passwd
    
    #示例三:显示磁盘利用率
    [root@xuzhichao ~]# df | awk -F% '$0~/^\/dev\//{print $1}' | awk '{print $1,$NF}'
    /dev/mapper/centos-root 10
    /dev/sda1 34
  • 逻辑操作符

    操作符说明
    &&逻辑“与”,表示前后两个表达式都为真,则结果为真。
    ||逻辑“或”,表示前后两个表达式有任意一个为真,则结果为真。
    !逻辑“非”,取反操作
    #示例一:去除/etc/httpd/conf/httpd.conf中所有注释行和空行
    [root@xuzhichao ~]# awk '$0 !~ /^[[:space:]]*#/ && $0 !~ /^$/' /etc/httpd/conf/httpd.conf 
    
    #示例二:
    [root@xuzhichao ~]# awk -F: '$3==0 || $3>1000' /etc/passwd
    [root@xuzhichao ~]# awk -F: '!($3==0)' /etc/passwd
  • 条件表达式(三目表达式)

    selector?if-true-expression:if-false-expression

    上述表达式中selector是一个判断语句,如果该判断语句结果为真,则执行if-true-expression,为假则执行if-false-expression

    #判断系统中的用户是系统用户还是普通用户
    [root@xuzhichao ~]# awk -F: '{$3>1000?usertype="common user":usertype="system user";printf "%-20s | %-20s\n",$1,usertype}' /etc/passwd
    root                 | system user         
    bin                  | system user         
    ...(省略部分内容)...        
    apache               | system user         
    admroot              | common user         
    rooter               | common user

1.7 awk的控制语句

awk脚本中支持复杂的控制语句,如同编程语言一样,支持判断,循环等语法。

1.7.1 if-else判断语句

语法格式为:

if(condition){statement1}else{statement2}
if(condition1){statement1}else if(condition2){statement2}else{statement3}

说明:如果符合condition1的条件,则执行statement1,若符合condition2的条件,则执行statement2的动作,否则执行statement3动作。

if-else判断语句使用示例如下:

#示例一:打印UID大于100的用户和UID
[root@xuzhichao ~]# awk '{if($3>=100)print $1,$3}' /etc/passwd

#示例二:
[root@xuzhichao ~]# awk 'BEGIN{test=100;if(test>=90){print "very good"}else if(test=60){print "good"}else{print "bad"}}'
very good

1.7.2 while循环语句

while循环语句的语法为:

while (condition){statement}

说明:符合条件condition则进入循环体statement中
#示例:统计/etc/grub2.cfg文件linux16这一行所有大于10个字符的单词和其字符数
[root@xuzhichao ~]# awk '/^[[:space:]]+linux16/{i=1;while(i10){print $i,length($i)};i++}}' /etc/grub2.cfg
/vmlinuz-3.10.0-1127.el7.x86_64 31
root=/dev/mapper/centos-root 28
crashkernel=auto 16
spectre_v2=retpoline 20
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
net.ifnames=0 13
/vmlinuz-0-rescue-d9abb103c9b943b0bfbbb212ed3dee7f 50
root=/dev/mapper/centos-root 28
crashkernel=auto 16
spectre_v2=retpoline 20
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
net.ifnames=0 13

1.7.3 do while循环语句

do while循环语句的语法为:

do {statement}while(condition)

说明:无论条件真假,先执行一次statement,如果符合条件condition则进入循环体statement中
#示例一:从1加到100的所有数字之和
[root@xuzhichao ~]# awk 'BEGIN{i=1;do{total+=i;i++}while(i<=100);print total}'
5050

1.7.4 for循环语句

for循环语句的语法为:

for(变量;条件;计数器) {运行代码;}

说明:变量用于定义变量初始值;
条件为设定进入循环的条件;
计数器用于对变量的值改变;
运行代码为循环体
#示例一:计算从1加到100的数字之和。
[root@xuzhichao ~]# awk 'BEGIN{for(i=1;i<=100;i++){total+=i};print total}'
5050

#示例二:使用time命令查看命令执行的时间
[root@xuzhichao ~]# time (for((i=1;i<=100000;i++));do let total+=i;done;echo $total )
5000050000

real	0m0.681s
user	0m0.630s
sys	0m0.052s
[root@xuzhichao ~]# time (awk 'BEGIN{for(i=1;i<=100000;i++){total+=i};print total}')
5000050000

real	0m0.013s
user	0m0.009s
sys	0m0.004s
[root@xuzhichao ~]# time (seq -s "+" 100000 |bc )
5000050000

real	0m0.045s
user	0m0.032s
sys	0m0.015s
#使用以上三种方式的结论为awk的执行效率要高于shell脚本方式

1.7.5 break和continue的用法

  • break:退出正在执行的循环体。

  • Continue:仅退出正在执行的循环体的本次循环,继续执行下一次循环。

  • break [n] :退出正在执行的n层循环体。

  • continue [n] : 退出n层循环。

    #示例一:计算1到100之间的所有奇数的总和
    [root@xuzhichao ~]# awk 'BEGIN{for(i=1;i<=100;i++){if(i%2==0)continue;total+=i};print total}'
    2500
    
    #示例二:计算1到100之间的所有奇数的总和
    [root@xuzhichao ~]# awk 'BEGIN{for(i=1;i<=100;i++){if(i%2==1)continue;total+=i};print total}'
    2550
    
    #示例三:如果使用break,则会直接退出循环体
    [root@xuzhichao ~]# awk 'BEGIN{for(i=1;i=10){system("iptables -A INPUT -s " i " -j REJECT")}}}' /var/log/httpd/access_log 
    [root@xuzhichao ~]# iptables -vnL
    Chain INPUT (policy ACCEPT 42 packets, 2772 bytes)
     pkts bytes target     prot opt in     out     source               destination         
        0     0 REJECT     all  --  *      *       192.168.20.17        0.0.0.0/0            reject-with icmp-port-unreachable
    
    Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
     pkts bytes target     prot opt in     out     source               destination         
    
    Chain OUTPUT (policy ACCEPT 22 packets, 2184 bytes)
     pkts bytes target     prot opt in     out     source               destination  
     
    #示例四:统计一篇文章中每个单词出现的次数,单词都是以空格作为分隔符的字母或数字。
    [root@xuzhichao ~]# awk '{for(i=1;i<=NF;i++){word[$i]++}}END{for(j in word){if(j ~ /\/){print j,word[j]}}}' /etc/fstab
    
    #示例五:分别统计男生和女生的平均成绩
    [root@xuzhichao ~]# cat f1
    name  score  age
    a      100   w
    b      90    m
    c      89    w
    d      80    w
    c      78    m
    e      70    m
    [root@xuzhichao ~]# awk 'BEGIN{printf "sex   avescore\n"} !/^name/{sum[$3]+=$2;num[$3]++} END{for(i in sum){printf "%-3s   %-4.2f\n",i,sum[i]/num[i]}}' f1
    sex   avescore
    w     89.67
    m     79.33

1.9 awk的函数

awk支持函数,分为内置函数和自定义函数。

1.9.1 awk内置函数

awk常用的内置函数如下:

  • length([s]):返回字符串 s 的长度;如果没有指定的话,返回 $0 的长度。

    [root@xuzhichao ~]# awk -v var="abcdefg" 'BEGIN{print length(var)}'
    7
  • rand():返回0和1之间的一个随机数

  • int():返回一个整数

    #示例:取0-100之间的整数,取5次。int()函数用于取整。
    [root@xuzhichao ~]# awk 'BEGIN{srand();for(i=1;i/{split($5,ip,":");count[ip[1]]++}END{for(i in count)print i,count[i]}'
    192.168.20.1 2
    0.0.0.0 7
  • system():用于在awk脚本中执行shell中的命令

    shell的命令内容使用双引号括起来,如果要使用awk的变量,变量不使用引号。

    使用示例如下:

    #示例一:
    [root@xuzhichao ~]# awk 'BEGIN{system("who")}'
    root     pts/0        May 22 11:04 (192.168.20.1)
    root     pts/1        May 23 21:53 (192.168.20.1)
    
    #示例二:注意is后面的空格符不可省略。
    [root@xuzhichao ~]# awk 'BEGIN{value=100;system("echo your score is " value)}'
    your score is 100

1.9.2 awk自定义函数

除了awk 中的内建函数,还可以在 awk 脚本程序中自定义函数,创建自定义函数的基本格式为:

function 函数名(参数1,参数2,…){     
	运行代码;
	return expression;
}

return用于定义函数的返回值

注意:在定义函数时,函数必须出现在所有代码块之前,包括 BEGIN 和 END代码块。

函数的使用示例如下:

#示例:其中var1,var2为形参,v1,v2为实参。
[root@xuzhichao ~]# cat awk.txt 
function max(var1,var2){
	var1>var2?var=var1:var=var2
	return var
}
BEGIN{max(v1,v2);print var}

[root@xuzhichao ~]# awk -v v1=100 -v v2=200 -f awk.txt 
200