Linux文本编辑三剑客之---awk的使用
1、awk
1.1 认识awk
awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。
awk其实不仅仅是工具软件,还是一种编程语言。不过,本文只介绍它的命令行用法,对于大多数场合,应该足够用了。
1.2 使用awk
1.2.1 语法
awk
[options]
'program'
var=value
file``…
awk
[options] -f programfile var=value
file``…
awk
[options]
'BEGIN{ action;… } pattern{ action;… } END{ action;… }'
file
...
1.2.2 常用命令选项
- -F fs:fs指定输入分隔符,fs可以是字符串或正则表达式,如-F:
- -v var=value:赋值一个用户定义变量,将外部变量传递给awk
- -f scripfile:从脚本文件中读取awk命令
1.3 awk变量
变量:内置和自定义变量,每个变量前加 -v 命令选项
1.3.1 内置变量
(1)格式
- FS :输入字段分隔符,默认为空白字符
- OFS :输出字段分隔符,默认为空白字符
- RS :输入记录分隔符,指定输入时的换行符,原换行符仍有效
- ORS :输出记录分隔符,输出时用指定符号代替换行符
- NF :字段数量,共有多少字段, (NF-1)引用倒数第2列
- NR :行号,后可跟多个文件,第二个文件行号继续从第一个文件最后行号开始
- FNR :各文件分别计数, 行号,后跟一个文件和NR一样,跟多个文件,第二个文件行号从1开始
- FILENAME :当前文件名
- ARGC :命令行参数的个数
- ARGV :数组,保存的是命令行所给定的各参数,查看参数
$ cat awkdemo
hello:world
linux:redhat:lalala:hahaha
kevin:love:youou
- -FS :输入字段分隔符,默认为空白字符
$ awk -v FS=":" '{print $1,$2}' awkdemo
hello world
linux redhat
kevin love
当然也可以写成另外一种形式:
$ awk -F: '{print $1,$2}' awkdemo
hello world
linux redhat
kevin love
另外通过awk命令可以指定自己所需要的字符,比如e:
awk -v FS="e:" '{print $1,$2}' awkdemo
hello:world
linux:redhat:lalala:hahaha
kevin:lov youou
但是如果用cut
命令的话会导致报错,因为通过cut
指定的分割符只能是单个字符,但是awk
可以指定多个字符作为分割符
$ cut -d "e:" -f1,2 awkdemo
cut: the delimiter must be a single character
Try 'cut --help' for more information.
- -OFS指定输出分隔符
awk -v FS=':' -v OFS='---' '{print $1,$2}' awkdemo
这样便通过awk的方法将文本中的:
分隔符变成---
hello---world
linux---redhat
kevin---love
- NF :字段数量,共有多少字段, $NF引用最后一列,$(NF-1)引用倒数第2列
$ awk -F: {'print NF'} awkdemo
2
4
3
- NR :行号,后可跟多个文件,第二个文件行号继续从第一个文件最后行号开始
$ awk '{print NR}' awkdemo awkdemo
1
2
3
4
5
6
- FNR :各文件分别计数, 行号,后跟一个文件和NR一样,跟多个文件,第二个文件行号从1开始
$ awk '{print FNR}' awkdemo awkdemo
1
2
3
1
2
3
- 可以直接通过awk来统计这两个文件一共多少行
$ awk END'{print NR}' awkdemo awkdemo
6
- FILENAME :当前文件名
$ awk "{print FILENAME}" awkdemo demo
awkdemo
awkdemo
awkdemo
demo
demo
demo
demo
1.3.2 自定义变量
自定义变量( 区分字符大小写)
(1)-v var=value
① 先定义变量,后执行动作print
相当于在我的当前的文件的前面可以添加任何我想要的字段
$ awk -v name="kevin" -F: '{print name":"$0}' awkdemo
kevin:hello:world
kevin:linux:redhat:lalala:hahaha
kevin:kevin:love:youou
② 在执行动作print后定义变量
$ awk -F: '{print name":"$0, name="kevin"}' awkdemo
:hello:world kevin
kevin:linux:redhat:lalala:hahaha kevin
kevin:kevin:love:youou kevin
(2)在program 中直接定义
可以把执行的动作放在脚本中,直接调用脚本 -f
$ awk -F: -f awk.txt awkdemo
kevin hello
kevin linux
kevin kevin
1.4 printf命令
比print更强大
1.4.1 格式
(1)格式化输出
printf
"FORMAT"``, item1,item2, ...
① 必须指定FORMAT
② 不会自动换行,需要显式给出换行控制符,\n
③ FORMAT 中需要分别为后面每个item 指定格式符
(2)格式符:与item 一一对应
- %c: 显示字符的ASCII码
- %d, %i: 显示十进制整数
- %e, %E: 显示科学计数法数值
- %f :显示为浮点数,小数 %5.1f,带整数、小数点、整数共5位,小数1位,不够用空格补上
- %g, %G :以科学计数法或浮点形式显示数值
- %s :显示字符串;例:%5s最少5个字符,不够用空格补上,超过5个还继续显示
- %u :无符号整数
- %%: 显示% 自身
(3)修饰符:放在%c[/d/e/f...]之间
-
#[.#]:
第一个数字控制显示的宽度;第二个# 表示小数点后精度,%5.1f - -:左对齐(默认右对齐) %-15s
- +:显示数值的正负符号 %+d
1.4.2 演示
当使用print
的时候:
$ awk -F: '{print $1,$3}' /etc/passwd | head -n 2
root 0
bin 1
- 第一列显示小于20的字符串;第2列显示整数并换行
$ awk -F: '{printf "%20s---%u\n",$1,$3}' /etc/passwd | head -n 2
root---0
bin---1
- 使用-进行左对齐;第2列显示浮点数
$ awk -F: '{printf "%-20s---%-15.3f\n",$1,$3}' /etc/passwd | head -n 2
root ---0.000
bin ---1.000
1.5 操作符
1.5.1 格式
- 算术操作符:
- 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,如果符合执行 ? 后的操作;否则执行 : 后的操作
1.5.2 演示
(1)模式匹配符
- 模式匹配符:~ :左边是否和右边匹配包含 !~ :是否不匹配
$ df -h |awk -F: '$0 ~ /^\/dev/'
/dev/mapper/cl-root 165G 81G 84G 50% /
/dev/sdb1 37T 30T 7.0T 81% /GP
/dev/sda1 197M 155M 43M 79% /boot
/dev/mapper/cl-var 50G 1.8G 49G 4% /var
/dev/sdc1 3.7T 2.9T 788G 79% /media/sdisk/usb2
/dev/sdd1 1.9T 1.2T 728G 61% /media/sdisk/usb3
这种操作模式有些类似于
$ df -h |awk -F: '$0' | grep '/dev/'
tmpfs 126G 21G 106G 17% /dev/shm
/dev/mapper/cl-root 165G 81G 84G 50% /
/dev/sdb1 37T 30T 7.0T 81% /GP
/dev/sda1 197M 155M 43M 79% /boot
/dev/mapper/cl-var 50G 1.8G 49G 4% /var
/dev/sdc1 3.7T 2.9T 788G 79% /media/sdisk/usb2
/dev/sdd1 1.9T 1.2T 728G 61% /media/sdisk/usb3
结合之前提到的NF :字段数量,共有多少字段, (NF-1)引用倒数第2列
只显示磁盘使用状况和磁盘名
$ df -h | awk '$0 ~ /^\/dev/ {print $(NF-1)"---"$1}'
50%---/dev/mapper/cl-root
81%---/dev/sdb1
79%---/dev/sda1
4%---/dev/mapper/cl-var
79%---/dev/sdc1
61%---/dev/sdd1
找出磁盘占比大于40%的
相当于在之前的命令的基础上添加了应该对第一行的筛选
$ df -h | awk '$0 ~ /^\/dev/ {print $(NF-1)"---"$1}' |awk -F% '$1>40'
50%---/dev/mapper/cl-root
81%---/dev/sdb1
79%---/dev/sda1
79%---/dev/sdc1
61%---/dev/sdd1
(2)逻辑操作符
$ awk -F: '$3 >=6 && $3<=66 {print $1,$3}' /etc/passwd
shutdown 6
halt 7
mail 8
uucp 10
operator 11
games 12
gopher 13
ftp 14
rpc 32
oprofile 16
rpcuser 29
gdm 42
ntp 38
apache 48
mailnull 47
smmsp 51
mysql 27
pegasus 66
postgres 26
$ awk -F: '$3==0 || $3>1000 {print $1,$3}' /etc/passwd
root 0
nfsnobody 65534
(3)条件表达式(三目表达式)
- 先判断selector,如果符合执行 ? 后的操作;否则执行 : 后的操作
$ awk -F: '{$3 >= 1000?usertype="common user":usertype="sysadmin user";print usertype,$1,$3}' /etc/passwd | head -n 30
sysadmin user root 0
sysadmin user bin 1
sysadmin user daemon 2
sysadmin user adm 3
sysadmin user lp 4
sysadmin user sync 5
sysadmin user shutdown 6
sysadmin user halt 7
sysadmin user mail 8
sysadmin user uucp 10
sysadmin user operator 11
sysadmin user games 12
sysadmin user gopher 13
sysadmin user ftp 14
sysadmin user nobody 99
sysadmin user dbus 81
sysadmin user usbmuxd 113
sysadmin user rpc 32
sysadmin user oprofile 16
sysadmin user vcsa 69
sysadmin user rtkit 499
sysadmin user abrt 173
sysadmin user hsqldb 96
sysadmin user avahi-autoipd 170
sysadmin user saslauth 498
sysadmin user postfix 89
sysadmin user rpcuser 29
common user nfsnobody 65534
sysadmin user haldaemon 68
sysadmin user gdm 42
1.6 awk PATTERN 匹配部分
1.6.1 格式
PATTERN:根据pattern 条件,过滤匹配的行,再做处理
(1)如果未指定:空模式,匹配每一行
(2)/regular expression/ :仅处理能够模式匹配到的行,正则,需要用/ / 括起来
(3)relational expression:关系表达式,结果为“真”才会被处理
真:结果为非0值,非空字符串
假:结果为空字符串或0值
(4)line ranges:行范围
startline(起始行),endline(结束行):/pat1/,/pat2/ 不支持直接给出数字,可以有多段,中间可以有间隔
(5)BEGIN/END 模式
BEGIN{}: 仅在开始处理文件中的文本之前执行一次
END{} :仅在文本处理完成之后执行
1.6.2 演示
$ awk -F: '{print $1,$2}' awkdemo
hello world
linux redhat
kevin love
- /regular expression/ :仅处理能够模式匹配到的行,正则,需要用/ / 括起来
$ awk -F: '/kevin/{print $1,$2}' awkdemo
kevin love
relational expression:关系表达式,结果为“真”才会被处理 ; 真:结果为非0值,非空字符串
$ awk -F: "1{print $1}" awkdemo
hello:world
linux:redhat:lalala:hahaha
kevin:love:youou
结果为空字符串或0值时,以下代码则不会被执行
$ awk -F: "0{print $1}" awkdemo
- 匹配第一个首字母为
h
到第一个首字母为a
的之间所有字符
$ awk -F: '/^h/,/^a/ {print $1}' awkdemo
hello
linux
kevin
- BEGIN/END 模式
- BEGIN{}: 仅在开始处理文件中的文本之前执行一次
- END{} :仅在文本处理完成之后执行
$ awk -F: 'BEGIN{print "第一列"}{print $1} END{print "结束"}' awkdemo
第一列
hello
linux
kevin
结束
1.7 awk有意思的案例
$ seq 10
1
2
3
4
5
6
7
8
9
10
因为i=1为真,所以全部打印
$ seq 10 | awk 'i=1'
1
2
3
4
5
6
7
8
9
10
因为i=0为假,所以不打印
$ seq 10 | awk 'i=0'
只打印奇数行;奇数行i进入时本身为空,被赋为!i,即不为空,所以打印;偶数行i进入时本身不为空,被赋为!i,即为空,所以不打印
$ seq 10 | awk 'i=!i'
1
3
5
7
9
解释上一个操作,i在奇偶行的时候到底是一个什么样的值
$ seq 10 |awk '{i=!i;print i}'
1
0
1
0
1
0
1
0
1
0
当然你也可以选择只打印偶数行,相当于是上边打印奇数行的取反
$ seq 10 | awk '!(i=!i)'
2
4
6
8
10
当然为了打印出偶数行,我们也可以对i
进行赋值,这样i
在最开始的时候就不为空,刚好与打印奇数行的时候相反
$ seq 10 | awk -v 'i=1' 'i=!i'
2
4
6
8
10
1、awk高阶用法
1.1 awk控制语句—if-else判断
(1)语法
if
(condition){statement;…}
[else statement] 双分支
if
(condition1){statement1}
else
if
(condition2){statement2}
else
{statement3} 多分支
(2)使用场景:对awk 取得的整行或某个字段做条件判断
(3)演示
$ awk -F: '{if($3>10 && $3<20)print $1,$3}' /etc/passwd
operator 11
games 12
gopher 13
ftp 14
oprofile 16
打印出所有路径为/bin/bash
的所有用户名以及路径
$ awk -F: '{if($NF=="/bin/bash") print $1,$NF}' /etc/passwd | head -n 10
root /bin/bash
chenys /bin/bash
liutao /bin/bash
git-admin /bin/bash
sysadmin /bin/bash
zhanglc /bin/bash
mysql /bin/bash
changlp /bin/bash
zhanghc /bin/bash
wangt /bin/bash
输出总例数大于3的行
$ awk -F: '{if(NF>2) print $0}' awkdemo
linux:redhat:lalala:hahaha
kevin:love:youou
第三列大于等于100的为Common user用户, 反之则为root or Sysuser用户
$ awk -F: '{if($3>=100) {printf "Common user: %s\n",$1} else{printf "root or Sysuser : %s\n",$1}}'
/etc/passwd | head -n 20
root or Sysuser : root
root or Sysuser : bin
root or Sysuser : daemon
root or Sysuser : adm
root or Sysuser : lp
root or Sysuser : sync
root or Sysuser : shutdown
root or Sysuser : halt
root or Sysuser : mail
root or Sysuser : uucp
root or Sysuser : operator
root or Sysuser : games
root or Sysuser : gopher
root or Sysuser : ftp
root or Sysuser : nobody
root or Sysuser : dbus
Common user: usbmuxd
root or Sysuser : rpc
root or Sysuser : oprofile
root or Sysuser : vcsa
找到磁盘利用率超过40的设备名以及利用率
$ df -h | awk -F% '/^\/dev/ {print $1}' | awk '$NF>40{print $1,$NF}'
/dev/sda3 63
/dev/sdb2 74
/dev/sda2 69
做一个判断,当test
>=90的时候为excellent,当60 < test
<90的时候为pass,当test
<60的时候为failed
$ awk 'BEGIN{test=100; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
excellent
$ awk 'BEGIN{test=55; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
failed
$ awk 'BEGIN{test=65; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
pass
1.2 awk控制语句—while循环
(1)语法
while``(condition){statement;…}
注:条件“真”,进入循环;条件“假”, 退出循环
(2)使用场景
对一行内的多个字段逐一类似处理时使用
对数组中的各元素逐一处理时使用
(3)演示
$ awk -F: '/^kevin/{i=1; while(i<=NF){print $i, length($i); i++}}' awkdemo
kevin 5
love 4
youou 5
以:
为分隔,显示每一行的长度大于6的单词和其长度
$ awk -F: '{i=1;while(i<=NF) {if(length($i)>=6){print $i,length($i)}; i++}}'
awkdemo
redhat 6
lalala 6
hahaha 6
计算从1加到100的和
$ awk 'BEGIN{i=1;sum=0;while(i<=100){sum=sum+i;i++} {print sum}}'
5050
1.3 awk控制语句—do-while循环
(1)语法
do
{statement;…}
while
(condition)
意义:无论真假,至少执行一次循环体
(2)计算1+2+3+...+100=5050
$ awk 'BEGIN{i=1; sum=0; do{sum=sum+i;i++}while(i<=100) {print sum}}'
5050
1.4 awk控制语句—for循环
(1)语法
for``(expr1;expr2;expr3) {statement;…}
(2)特殊用法:遍历数组中的元素
for``(var
in
array) {``for``-body}
(3)演示
$ awk -F: '{for(i=1;i<=NF;i++){print $i,length($i)}}' awkdemo
hello 5
world 5
linux 5
redhat 6
lalala 6
hahaha 6
kevin 5
love 4
youou 5
$ cat sort.txt
xiaoming m 90
xiaohong f 93
xiaohei m 80
xiaofang f 99
统计男m、女f 各自的平均分
$ awk '{m[$2]++; score[$2]+=$3} END{for(i in m){printf "%s:%6.2f\n",i,score[i]/m[i]}}' sort.txt
m: 85.00
f: 96.00
1.5 数值\字符串处理
(1)数值处理
- rand():返回0和1之间一个随机数,需有个种子 srand(),没有种子,一直输出0.237788
演示:
$ awk 'BEGIN{print rand()}'
0.237788
当加了srand()之后,就可以正常输出随机数了
$ awk 'BEGIN{srand();print rand()}'
0.625523
$ awk 'BEGIN{srand();print rand()}'
0.592696
$ awk 'BEGIN{srand(); print int(rand()*100%50)+1}'
21
(2)字符串处理:
- length([s]) :返回指定字符串的长度
- sub(r,s,[t]) :对t 字符串进行搜索r 表示的模式匹配的内容,并将第一个匹配的内容替换为s
- gsub(r,s,[t]) :对t 字符串进行搜索r 表示的模式匹配的内容,并全部替换为s 所表示的内容
- split(s,array,[r]) :以r 为分隔符,切割字符串s ,并将切割后的结果保存至array 所表示的数组中,第一个索引值为1, 第二个索引值为2,…
演示:
将前面文本中的第一个:
改变成-
$ echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
2008-08:08 08:08:08
$ echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
2008-08-08 08-08-08
- split(s,array,[r]) :以r 为分隔符,切割字符串s ,并将切割后的结果保存至array 所表示的数组中,第一个索引值为1, 第二个索引值为2,…
$ echo "2008:08:08 08:08:08" | awk '{split($0,i,":")} {for(n in i){print n,i[n]}}'
1 2008
2 08
3 08 08
4 08
5 08
1.8 awk中调用shell 命令
(1)system 命令
空格是awk 中的字符串连接符,如果system中需要使用awk中的变量可以使用空格分隔,或者说除了awk 的变量外其他一律用"" 引用 起来。
$ awk 'BEGIN{system("hostname")}'
R290-1.GenePlus
$ awk 'BEGIN{score=100; system("echo your score is " score)}'
your score is 100