Linux文本三剑客之awk命令


纸上得来终觉浅,绝知此事要躬行。

Linux文本三剑客之awk命令


1. 什么是awk

**awk**是一个报告生成器,可以格式化文本输出。

  • 通常我们在Linux下使用的awk,其实是gawk
  • awk默认以空格作为分隔符号,整个字段使用$0来代替,其子字段使用$1$2等等,以此类推

实战演示

[root@localhost ~]# which awk
/bin/awk

[root@localhost ~]# ll /bin/awk
lrwxrwxrwx. 1 root root 4 5月   7 21:04 /bin/awk -> gawk
# 如果不添加这里的逗号(,)则没有下面的空格隔开
[root@localhost ~]# tail -5 /etc/fstab | awk '{print $2, $3}'
swap swap
/dev/shm tmpfs
/dev/pts devpts
/sys sysfs
/proc proc

2. 使用格式

语法

  • awk [options] 'program' FILE ... - program: PATTERN{ACTION STATEMENTS} - PATTERN表示匹配模式 - {ACTION STATEMENTS}表示动作语句 - ACTION可以有多个语句,使用分号分隔 - 这里的program类似于 Bash 中命令,如printprintf - 选项: - -F:指明输入时用到的字段分隔符 - 默认使用空格作为分隔符,这里的空格不限制长度 - 如果是但分隔符,不需要指定 - [%' ']表示%和空格作为分隔符 - -v var=value: 自定义变量

3. 命令详解

3.1 print命令

格式

  • print item1, item2, ...

要点

  • (1) 逗号分隔符,默认输出空格作为分隔符
  • (2) 输出的各item可以字符串、数值、当前记录的字段、变量或awk的表达式
  • (3) 如省略item,相当于print $0,打印整行字符
  • (4) 在awk中变量替换需要在引号之外才能生效

实战演示

[root@localhost ~]# tail -5 /etc/fstab | awk '{print "Hello:",$2,$3,6}'
Hello: swap swap 6
Hello: /dev/shm tmpfs 6
Hello: /dev/pts devpts 6
Hello: /sys sysfs 6
Hello: /proc proc 6
[root@localhost ~]# tail -5 /etc/fstab | awk '{print "Hello:"$2}'
Hello:swap
Hello:/dev/shm
Hello:/dev/pts
Hello:/sys
Hello:/proc

[root@localhost ~]# tail -5 /etc/fstab | awk '{print "Hello:$2"}'
Hello:$2
Hello:$2
Hello:$2
Hello:$2
Hello:$2
# 如省略`item`,相当于`print $0`,打印整行字符
[root@localhost ~]# tail -5 /etc/fstab | awk '{print}'
/dev/mapper/VolGroup-lv_swap swap                    swap    defaults        0 0
tmpfs                   /dev/shm                tmpfs   defaults        0 0
devpts                  /dev/pts                devpts  gid=5,mode=620  0 0
sysfs                   /sys                    sysfs   defaults        0 0
proc                    /proc                   proc    defaults        0 0
# 这里打印出来5行空白,是因为awk本身就是遍历整个文本的
[root@localhost ~]# tail -5 /etc/fstab | awk '{print ""}'

3.2 变量

内建变量

  • FS - input field seperator,指定输入时的分隔符,默认为空白字符
  • OFS - output field seperator,指定输出时的分隔符,默认为空白字符
  • RS - input record seperator,输入时的换行符,默认为换行符
  • ORS - output record seperator,输出时的换行符,默认为换行符
  • NF - number of field,每一行的字段数量
  • NR - number of record,文件中的行数
  • FNR - 各文件分别计数行数
  • FILENAME - 显示当前文件名
  • ARGC - 命令行参数的个数
  • ARGV - 是一个数组,保存的是命令行所给定的各参数

自定义变量

  • (1) -v var=value - 变量名区分字符大小写
  • (2) 在program中直接定义变量 - 变量用到的时候直接定义

实战演示

# 指明输出分隔符为":"
[root@localhost ~]# awk -v FS=':' '{ print $1 }' /etc/passwd
root
bin
daemon

[root@localhost ~]# awk -F: '{ print $1 }' /etc/passwd
root
bin
daemon
# 指定输出时的分隔符为":",默认的为空格
[root@localhost ~]# awk -v FS=':' -v OFS=':' '{ print $1,$3,$7 }' /etc/passwd
root:0:/bin/bash
bin:1:/sbin/nologin
daemon:2:/sbin/nologin
...
# 这里表示再输入的时候将空格也看作为换行符,把默认的原有换行符也看成换行符
[root@localhost ~]# awk -v RS=' ' '{ print }' /etc/passwd
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@localhost ~]# awk '{ print NF }' /etc/fstab
0
1
2
10
...

# 加上$,相当于打印对应数值的字段,以空格作为分隔符
[root@localhost ~]# awk '{ print $NF }' /etc/fstab

#
/etc/fstab
2016
...
# 这里打印出行号
[root@localhost ~]# awk '{ print NR }' /etc/fstab
1
2
3
...

# 当跟上多个文件的时候,就表示多个文件的共同计数
[root@localhost ~]# awk '{ print NR }' /etc/fstab /etc/issue
1
2
3
...

# 多个文件,单独计数
[root@localhost ~]# awk '{ print FNR }' /etc/issue /etc/fstab
1
2
3
1
2
...
# 输出多行,这就是awk的循环功能了
[root@localhost ~]# awk '{ print FILENAME }' /etc/issue
/etc/issue
/etc/issue
/etc/issue
# 这里使用BEGIN,可以防止因为awk的特性的多次出现情况
# 第一个参数是awk,第二个参数是/etc/issue
[root@localhost ~]# awk 'BEGIN{ print ARGC }' /etc/issue
2

[root@localhost ~]# awk 'BEGIN{ print ARGV[0] }' /etc/issue
awk

[root@localhost ~]# awk 'BEGIN{ print ARGV[1] }' /etc/issue
/etc/issue
# 这里/etc/issue的意思就是表示打印的行数,如果只想打印一行的话,使用BEGIN
[root@localhost ~]# awk -v test='Hello awk' 'BEGIN{ print test }' /etc/issue
Hello awk
[root@localhost ~]# awk -v test='Hello awk' '{ print test }' /etc/issue
Hello awk
Hello awk
Hello awk

# 在`program`中直接定义变量
[root@localhost ~]# awk 'BEGIN{ test="Hello awk"; print test }'
Hello awk

3.3 printf命令

格式化输出

  • 格式
    • printf FORMAT, item1, item2, ...
  • 注意
    • (1) FORMAT必须给出
    • (2) 不会自动换行,需要显式给出换行控制符,\n
    • (3) FORMAT中需要分别为后面的每个item指定一个格式化符号

格式符

  • %c: 显示字符的ASCII
  • %d, %i: 显示十进制整数
  • %e, %E: 科学计数法数值显示
  • %f:显示为浮点数
  • %g, %G:以科学计数法或浮点形式显示数;
  • %s:显示字符串
  • %u:无符号整数
  • %%: 显示%自身

修饰符

  • #[.#] - 第一个#表示控制显示的宽度 - 第二个#表示小数点后的精度,如果不是小数可以省略 - -:表示左对齐,默认为右对齐 - +:显示数值的符号 - 如%3.1f - 如%3d

实战演示

# 默认需要引号引起来,并且没有换行符
[root@localhost ~]# awk -F: '{ printf "%s",$1 }' /etc/passwd
rootbindaemonadmlpsyncshutdownhaltmailuucpoperatorgamesgopherftpnobodydbususbmuxdvcsarpcrtkitavahi-autoipdabrtrpcusernfsnobodyhaldaemongdmntpapachesaslauthpostfixpulsesshdtcpdumpescapenamedmysql

# 换行符指定
[root@localhost ~]# awk -F: '{ printf "%s\n",$1 }' /etc/passwd
root
bin
daemon
...
# 定义输出
[root@localhost ~]# awk -F: '{ printf "Username:%s\n",$1 }' /etc/passwd
Username:root
Username:bin
Username:daemon
...

# 定义输出
[root@localhost ~]# awk -F: '{ printf "Username:%s, UID:%d\n", $1,$3 }' /etc/passwd
Username:root, UID:0
Username:bin, UID:1
Username:daemon, UID:2
...
# 格式化输出
[root@localhost ~]# awk -F: '{ printf "Username:%-10s, UID:%5d\n", $1,$3 }' /etc/passwd
Username:root      , UID:    0
Username:bin       , UID:    1
Username:daemon    , UID:    2

3.4 操作符

算术操作符

  • x+y
  • x-y
  • x*y
  • x/y
  • x^y
  • x%y
  • -x:表示负数
  • +x:把一个字符串转换为数值

字符串操作符

  • 没有符号的操作符,字符串连接

赋值操作符

  • =
  • +=
  • -=
  • *=
  • /=
  • %=
  • ^=
  • ++:自加
  • --:自减

比较操作符

  • >
  • >=
  • <
  • <=
  • !=
  • ==

模式匹配符

  • ~:做成的字符串是否能被右侧的模式所匹配
  • !~:是否不匹配

逻辑操作符

  • &&
  • ||
  • !

函数调用

  • function_name(argu1, argu2, ...)

条件表达式

  • selector ? if-true-expression : if-false-expression - 三目运算符,selector表示表达式 - 如果为真执行if-true-expression,如果为假执行if-false-expression

实战演示

# 条件表达式
[root@localhost ~]# awk -F: '{ $3>=1000?usertype="Common User":usertype="Sysadmin or SysUser"; printf "%15s:%-s\n", $1,usertype }' /etc/passwd
           root:Sysadmin or SysUser
            bin:Sysadmin or SysUser
         daemon:Sysadmin or SysUser
...

3.5 PATTERN匹配

类似于正则表达式中的定界符

  • (1) empty - 空模式 - 会匹配文本的每一行

  • (2) /regular expression/ - 正则表达式匹配 - 仅处理能够被此处的模式匹配到的行

  • (3) relational expression - 关系表达式 - 结果有,结果为才会被处理 - 假值为结果为0值或空字符串 - 真值为结果为非0值或非空字符串

  • (4) line ranges - 行范围 - 地址定界 - 如startline,endline ==> /pat1/,/pat2/ - 注意,不支持直接给出数字的格式,需要使用NR

  • (5) BEGIN/END模式 - awk的特点就是自动遍历文本的每一行,可以使用BEGIN/END进行限制 - BEGIN{} - 用于打印行首信息 - 仅在开始处理文件中的文本之前执行一次

        - `END{}`
            - 用于处理结果
            - 仅在文本处理完成之后执行一次
    
# 正则表达式匹配
[root@localhost ~]# awk '/^UUID/{ print $1 }' /etc/fstab
UUID=6d0f6433-eed7-4cb2-81f5-2c4feaad2c18

# 正则表达式匹配,取反
[root@localhost ~]# awk '!/^UUID/{ print $1 }' /etc/fstab
/dev/mapper/VolGroup-lv_root
/dev/mapper/VolGroup-lv_swap
tmpfs
...
# 关系表达式
[root@localhost ~]# awk -F: '$3>=500{ print $1,$3 }' /etc/passwd
nfsnobody 65534
escape 500

[root@localhost ~]# awk -F: '$3<500{ print $1,$3 }' /etc/passwd
root 0
bin 1
daemon 2
...
# 模式匹配,$NF表示以:分割开的最后一个字段
[root@localhost ~]# awk -F: '$NF=="/bin/bash"{ print $1,$3 }' /etc/passwd
root 0
escape 500
mysql 27

[root@localhost ~]# awk -F: '$NF~/bash$/{ print $1,$3 }' /etc/passwd
root 0
escape 500
mysql 27
# 地址定界
[root@localhost ~]# awk -F: '(NR>=2 && NR<=10){print $1}' /etc/passwd
bin
daemon
...

# 指明模式
[root@localhost ~]# awk -F: '/^root/,/^adm/{ print $1 }' /etc/passwd
root
bin
daemon
adm
# BEGIN模式
# 这里添加的BEGIN表示执行一次,如果不添加则每次多执行
[root@localhost ~]# awk -F: 'BEGIN{ print "-------------------\n| useranme    uid |\n-------------------"} { printf "%10s,%6s\n", $1,$3 }' /etc/passwd
-------------------
| useranme    uid |
-------------------
      root,     0
       bin,     1
    daemon,     2
...

# END模式
[root@localhost ~]# awk -F: 'BEGIN{ print "-------------------\n| useranme    uid |\n-------------------"} { printf "%10s,%6s\n", $1,$3 } END{ print "=======END=======\n" }' /etc/passwd
-------------------
| useranme    uid |
-------------------
      root,     0
       bin,     1
    daemon,     2
...
========END========

3.6 常用的action

  • (1) Expressions - 表达式
  • (2) Control statements - 控制语句,如ifwhile
  • (3) Compound statements - 组合语句
  • (4) input statements - 输入语句
  • (5) output statements - 输出语句

3.7 控制语句

格式

  • if(condition) {statments} - if语句
  • if(condition) {statments} else {statements} - if…else语句
  • while(conditon) {statments} - while语句
  • do {statements} while(condition) - do语句
  • for(expr1;expr2;expr3) {statements} - for语句
  • break
  • continue
  • delete array - 删除一个数组
  • delete array[index] - 从数字中删除一个指定元素
  • exit - 退出语句
  • { statements } - 组合语句需要花括号括起来使用

3.7.1 if-else语句

语法

  • if(condition) statement [else statement] - 组合语句需要花括号括起来使用

使用场景

  • awk取得的整行或某个字段做条件判断

实例展示

# 双分支的if语句,需要{}
[root@localhost ~]# 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
root or Sysuser: daemon
...
# 单分支的if语句
[root@localhost ~]# awk -F: '{ if($NF=="/bin/bash") print $1 }' /etc/passwd
root
escape
mysql
[root@localhost ~]# awk '{ if(NF>5) print $0 }' /etc/fstab
/dev/mapper/VolGroup-lv_root /                       ext4    defaults        1 1
UUID=6d0f6433-eed7-4cb2-81f5-2c4feaad2c18 /boot                   ext4    defaults        1 2
...
# 这里的[],可以指定多个分隔符,如[%' ']表示%和空格作为分隔符
[root@localhost ~]# df -h | awk -F[%] '/^\/dev/{print $1}' | awk '{ if($NF>=10) print $1 }'
/dev/sda1

3.7.2 while循环

语法

  • while(condition) statement - 条件”真”,进入循环 - 条件”假”,退出循环

使用场景

  • 对数组中的各元素逐一处理时使用
  • 对一行内的多个字段逐一类似处理时使用
  • 对于文本中的每行进行循环是不需要的,因为awk的特性

实例展示

# 在centos6中使用,其中内建函数length输出长度

[root@localhost ~]# awk '/^[[:space:]]*kernel/{ print }' /etc/grub.conf awk
    kernel /vmlinuz-2.6.32-573.26.1.el6.x86_64 ro root=/dev/mapper/VolGroup-lv_root rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD rd_LVM_LV=VolGroup/lv_swap SYSFONT=latarcyrheb-sun16 crashkernel=auto rd_LVM_LV=VolGroup/lv_root  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet

[root@localhost ~]# awk '/^[[:space:]]*kernel/{ i=1; while(i<=NF) { print $i, length($i); i++ } }' /etc/grub.conf
kernel 6
/vmlinuz-2.6.32-573.26.1.el6.x86_64 35
ro 2
root=/dev/mapper/VolGroup-lv_root 33
...

[root@localhost ~]# awk '/^[[:space:]]*kernel/{ i=1; while(i<=NF) { if(length($i)>=7) { print $i, length($i) }; i++ } }' /etc/grub.conf
/vmlinuz-2.6.32-573.26.1.el6.x86_64 35
root=/dev/mapper/VolGroup-lv_root 33
rd_NO_LUKS 10
LANG=en_US.UTF-8 16
rd_NO_MD 8
# centos 7
[root@localhost ~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg

3.7.3 do-while 循环

语法

  • do statement while(condition)

意义

  • 至少执行一次循环体

3.7.4 for 循环

语法

  • for(expr1;expr2;expr3) statement
  • for(variable assignment;condition;iteration process) {for-body}

特殊用法

  • 能够遍历数组中的元素
  • 语法for(var in array) {for-body}

实例展示

# centos6
[root@localhost ~]# awk '/^[[:space:]]*kernel/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub.conf
kernel 6
/vmlinuz-2.6.32-573.26.1.el6.x86_64 35
...

[root@localhost ~]# awk '/^[[:space:]]*kernel/{for(i=1;i<=NF;i++) { if(length($i)>=7) print $i,length($i)}}' /etc/grub.conf
/vmlinuz-2.6.32-573.26.1.el6.x86_64 35
root=/dev/mapper/VolGroup-lv_root 33
# centos7
[root@localhost ~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg

3.7.5 switch语句

语法

  • switch(expression) {case VALUE1 or /REGEXP/: statement; case VALUE2 or /REGEXP2/: statement; ...; default: statement} - 我们指定的表达式expression等于case中的值或模式匹配就执行对应的分支 - 组合语句需要花括号括起来 - default用于指定未匹配到的执行结果 - 以此类推

3.7.6 break 和 continue

  • break [n] - 这里的[n]表示跳出多少条循环
  • continue - 退出当前循环

3.7.7 next语句

意义

  • 用于结束awk的内生循环
  • 提前结束对本行的处理而直接进入下一行

实战演示

# 显示用户id号为偶数的用户
[root@localhost ~]# awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
root 0
daemon 2
lp 4
shutdown 6
...

3.8 数组array

关联数组

  • array[index-expression]

index-expression

  • (1) 可使用任意字符串,字符串要使用双引号 - 如weekdays[mon]="Monday"
  • (2) 如果某数组元素事先不存在,在引用时awk会自动创建此元素,并将其值初始化为空串 - 使用不要声明 - 判断数组中元素是否存在,不能以空作为判断 - 判断数组中是否存在某元素,要使用index in array格式进行

遍历数组

  • 若要遍历数组中的每个元素,要使用for循环
  • for(var in array) {for-body}

注意

  • var会遍历array的每个索引
  • 使用数据来统计次数 - state["LISTEN"]++ - state["ESTABLISHED"]++

实战演示

[root@localhost ~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday"; print weekdays["mon"]}'
Monday

[root@localhost ~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
Monday
Tuesday
# 统计netstat的状态统计次数
# 其中/^tcp\>/中的>表示词尾牟定,只匹配tcp开头的
[root@localhost ~]# netstat -tan | awk '/^tcp\>/{ print }'
tcp        0      0 0.0.0.0:3306                0.0.0.0:*                   LISTEN
tcp        0      0 172.16.242.179:22           172.16.242.1:60761          ESTABLISHED
...

# state[$NF]表示将匹配到的行尾作为索引创建数组
# state[$NF]++表示统计次数
# END表示遍历完之后执行的输出操作
[root@localhost ~]# netstat -tan | awk '/^tcp\>/{state[$NF]++}END{for(i in state) {print i,state[i]}}'
ESTABLISHED 3
LISTEN 15
# 统计Web服务器的客户端IP访问次数统计
[root@localhost ~]# awk '{ip[$1]++}END{for(i in ip) {print i,ip[i]}}' /var/log/httpd/access_log
# 练习1:统计/etc/fstab文件中每个文件系统类型出现的次数
[root@localhost ~]# awk '/^UUID/{fs[$3]++}END{for(i in fs) {print i,fs[i]}}' /etc/fstab

# 练习2:统计指定文件中每个单词出现的次数
# 这里表示对每一行执行{for(i=1;i<=NF;i++){count[$i]++}}
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){count[$i]++}}END{for(i in count) {print i,count[i]}}' /etc/fstab

3.9 函数

3.9.1 内置函数

数值处理

  • rand() - 返回 0 和 1 之间一个随机数

字符串处理

  • length([s]) - 返回指定字符串的长度
  • sub(r,s,[t]) - 第一次替换 - 以r表示的模式来查找t所表示的字符中的匹配的内容,并将其第一次出现替换为s所表示的内容
  • gsub(r,s,[t]) - 全局替换 - 以r表示的模式来查找 t 所表示的字符中的匹配的内容,并将其所有出现均替换为s所表示的内容
  • split(s,a[,r]) - 切割使用 - 以r为分隔符切割字符 s,并将切割后的结果保存至a所表示的数组中

实战演示

# 替换第一次匹配到$1中的小写o变为O
# 这里表示的是是否替换的返回值
[root@localhost ~]# awk -F: '{ print sub(o,O,$1) }' /etc/passwd
1
1
...
[root@localhost ~]# ab -c 100 -n 1000 http://172.16.242.180/index.html

# 对于得到的数组ip进行次数统计,count[ip[1]]++,最后输出即可
[root@localhost ~]# netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}'
 5
63.130.76.57 1
172.16.242.1 2
0.0.0.0 6

3.9.2 自定义函数

快去看看《sed和awk》这本书


4. 常用命令总结

OpenSource: 总结的快捷键使用文档!

Linux文本三剑客之awk命令


文章作者: Escape
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Escape !
  目录