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引用最后一列,(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引用最后一列,(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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容