awk
记录和字段
每个输入行作为一条记录,由空格或制表符分隔的单词作为字段(用来分隔字段的字符被称为分隔符),默认分隔符是空格。
连续的两个或多个空格和/制表符被称作一个分隔符
- 以下举例演示以下
[root@localhost ~]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 451M 0 451M 0% /dev
tmpfs 471M 0 471M 0% /dev/shm
tmpfs 471M 6.6M 464M 2% /run
tmpfs 471M 0 471M 0% /sys/fs/cgroup
/dev/mapper/cs-root 8.0G 5.0G 3.1G 62% /
/dev/sda1 1014M 176M 839M 18% /boot
tmpfs 95M 0 95M 0% /run/user/0
[root@localhost ~]# df -h | awk '{print $2}'
Size
451M
471M
471M
471M
8.0G
1014M
95M
//第二列之间有很多空格,默认分隔符是空格
字段和引用的分离
awk允许使用字段操作符**$来指定字段,在该操作符后面跟着一个数字或者变量,用于标识字段的位置。
$1表示第一个字段,$2表示第二个字段,以此类推,但是$0**表示整个输入记录。
- 以下举例演示一下使用:
//更换输入行的短语顺序
[root@localhost ~]# cat test
tom wuhan 111-222-333
[root@localhost ~]# awk '{print $2,$3,$1}' test
wuhan 111-222-333 tom
//$0
[root@localhost ~]# awk '{print $0}' test
tom wuhan 111-222-333
可以用任何计算值为整数的表达式来表示一个字段,而不只是用数字和变量。
- 以下举例演示使用整数的表达式:
[root@localhost ~]# echo 'a b c d'|awk 'BEGIN{one=1 ;two=2}{print $(one+two)}'
c
可以在命令行使用-F选项改变字段的分隔符,他后面跟着(或者紧跟着,或者有空白)分隔符
- 以下举例演示使用-F:
[root@localhost ~]# cat test
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 -F':' '{print $1,$2}' test
root x
bin x
daemon x
//-F后面指定的分隔符最好是使用引号,这样便于分辨
//可以指定任意自己需要的为分隔符,也可以含有正则表达式
[root@localhost ~]# awk -F'[0-9]+' '{print $1}' test
root:x:
bin:x:
daemon:x:
- 经典示例,取出主机IP地址
[root@localhost ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:03:ca:ed brd ff:ff:ff:ff:ff:ff
inet 192.168.218.133/24 brd 192.168.218.255 scope global dynamic noprefixroute ens160
valid_lft 1622sec preferred_lft 1622sec
inet6 fe80::6faa:c998:4390:2ee4/64 scope link noprefixroute
valid_lft forever preferred_lft forever
[root@localhost ~]# ip a|grep 'inet '|grep -v '127.0.0.1'
inet 192.168.218.133/24 brd 192.168.218.255 scope global dynamic noprefixroute ens160
//使用awk取出IP
[root@localhost ~]# ip a|grep 'inet '|grep -v '127.0.0.1'
inet 192.168.218.133/24 brd 192.168.218.255 scope global dynamic noprefixroute ens160
[root@localhost ~]# ip a|grep 'inet '|grep -v '127.0.0.1'|awk '{print $2}'|awk -F'/' '{print $1}'
192.168.218.133
//以上取出了主机IP,但是使用了两次awk,过程比较繁琐,以下我们使用一次awk取出,简单明了
[root@localhost ~]# ip a|grep 'inet '|grep -v '127.0.0.1'|awk -F'[ /]+' '{print $3}'
192.168.218.133
//把空格和/同时作为分隔符,以此来取出IP
在脚本中指定域分隔符是一个好习惯,可以定义系统变量FS来改变字段分隔符,必须在由BEGIN中来指定这个变量。
BEGIN {FS=","}
FS为输入分隔符,OFS为输出分隔符
我们在指定分隔符时使用**-F**,也可以使用FS来指定
[root@localhost ~]# cat test
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 'BEGIN{FS=":";OFS="-"}{print $2,$3}' test
x-0
x-1
x-2
注意:在使用awk时,外层使用单引号’’,内层使用双引号“ ”
可以编写匹配规则来修改操作符打印除特定的内容,可以使用正则表达式
//打印号码较为常见,以下举例演示
[root@localhost ~]# cat test
707-724-0000
(707) 724-0000
(707)724-0000
1-707-724-0000
1 707-724-0000
1(707)724-0000
//如果想要输出以上test的内容,我们尝试编写以下匹配规则
[root@localhost ~]# awk '/1?[ -]?\(?[0-9]+\)?[ -]?[0-9]+-[0-9]+/' test
//尝试取出test中的
(707) 724-0000
(707)724-0000
[root@localhost ~]# awk '/^\([0-9]+\) ?[0-9]+-[0-9]+/' test
(707) 724-0000
(707)724-0000
表达式
- 常用转译序列
序列 | 描述 |
---|---|
\n | 换行符 |
\t | 水平制表符(Tab) |
\r | 回车 |
- 算术操作符
操作符 | 描述 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取模 |
^ | 取幂 |
** | 平方 |
- 赋值操作符
操作符 | 定义 |
---|---|
++ | 变量加1 |
– | 变量减1 |
+= | 将加的结果赋给变量 |
-= | 将减的结果赋给变量 |
*= | 将乘的结果赋给变量 |
/= | 将除的结果赋给变量 |
%= | 将取模的结果赋给变量 |
^= | 将取幂的结果赋给变量 |
**= | 将取幂的结果赋给变量.a |
在awk中,变量没有赋予值时,默认是0
[root@localhost ~]# awk 'BEGIN{y=x+1;print y}'
1
以下举例演示用于计算一个文件中空行的目录:
//统计空行数量,test中有两行空行
[root@localhost ~]# cat test
707-724-0000
(707) 724-0000
(707)724-0000
1-707-724-0000
1 707-724-0000
1(707)724-0000
[root@localhost ~]# awk '/^$/{print x+=1}' test
1
2
既然可以匹配空行,那么就可以匹配需要的任意内容匹配,自己根据自己的需要来匹配
++x 在返回结果前递增x的值(前缀)
x++ 在返回结果后递增x的值(后缀)
[root@localhost ~]# awk '/^$/{print ++x}' test
1
2
[root@localhost ~]# awk '/^$/{print x++}' test
0
1
以上我们都是遇到空行时就会打印空行的数值,我们现在可以计算所有的空行的值后再打印空行的总数,在END模式中放置print语句,当读完最后一个空行后打印x的值:
[root@localhost ~]# awk '/^$/{++x}END{print x}' test
2
计算学生的平均值
[root@localhost ~]# cat test
tom 88 99 77 66 55
jerry 66 76 87 98 72
lisi 55 78 98 96 82
[root@localhost ~]# awk '{sum=$2+$3+$4+$5+$6;avg=sum/5;print $1,avg}' test
tom 77
jerry 79.8
lisi 81.8
系统变量
前面我们已经介绍了FS输入分隔符,OFS输出分隔符,他们的默认值是一个空格
NR表示行号
[root@localhost ~]# cat test
可乐
雪碧
冰红茶
矿泉水
奶茶
咖啡
//使用NR显示行号
[root@localhost ~]# awk '{print NR".",$1}' test
1. 可乐
2. 雪碧
3. 冰红茶
4. 矿泉水
5. 奶茶
6. 咖啡
- 编写一个脚本实现自动贩卖机的功能
[root@localhost ~]# cat test
可乐 3
雪碧 3
冰红茶 3
矿泉水 2
奶茶 4
咖啡 5
[root@localhost ~]# cat test.sh
#!/bin/bash
echo "商品列表:"
awk '{print NR".",$1,$2"元"}' test
read -p "请输入您要买的商品编号:" choice
read -p "请输入您要买的数量:" num
awk -vcount=$num -vline=$choice 'NR==line{print "您一共买了"count"瓶,共消费"$2*count"元。"}' test
[root@localhost ~]# ./test.sh
商品列表:
1. 可乐 3元
2. 雪碧 3元
3. 冰红茶 3元
4. 矿泉水 2元
5. 奶茶 4元
6. 咖啡 5元
请输入您要买的商品编号:2
请输入您要买的数量:2
您一共买了2瓶,共消费6元。