:35 - 13:17 (00:42)
wtmp begins Tue Apr 10 08:54:45 2018
若我想取出账号与登陆者的IP,且账号与IP之间以[Tab]
隔开,则会变成这样:
root@orion-orion:~ last -n 3 | awk '{print $1 "\t" $3}'
root 10.249.45.37
root 10.249.45.37
root 10.249.45.37
注意,awk
的所有后续操作都是以单引号括住的,而awk
的格式内容如果想要以print
打印时,记得将非变量的文字部分使用双引号括起来,因为单引号已经是awk
命令的固定用法了。此外,因为这里无论哪一行我们都要处理,因此就不需要有条件类型
的限制。
另外,由上面的例子我们看到,在awk
的括号内,每一行的每个字段都有变量名称($1
,$2
等)。在上面的例子中,root
位于第1栏,故其变量名称为$1
;而10.249.45.37
是第3栏,故它是$3
,后面以此类推。还有个变量比较特殊,那就是$0
,它表示一整行数据。由此可知,刚刚上面5行当中,整个awk
的处理流程就是:
- 一次性读入第1行整行的数据并存入
$0
,然后将其拆分为多个字段并写入$1
、$2
、$3
等变量当中。
- 根据
条件类型
的限制,判断是否需要进行后面的操作
(在上面这个例子中没有条件类型
)。
- 完成所有操作与条件类型。
- 若还有后续行的数据,则重复上面1~3的步骤,直到所有的数据都读完为止。
经过这样的步骤,我们看到了awk
是以行为一次处理的单位,而以字段为最小的处理单位。好了,那么如何快速地获得我们的数据有几行几列呢?这就需要awk
的内置变量的帮忙。
变量名称 |
代表意义 |
NF |
每一行(也即$0 )所拥有的字段总数 |
NR |
目前awk 所处理的是第几行数据 |
FS |
目前的分割字符,默认是空格键 |
我们继续以上面last -n 3
的例子来做说明,如果我想要:
- 列出每一行的账号(也就是
$1
);
- 列出目前处理的行数(就是
awk
内的NR
变量);
- 并且说明该行有多少字段(也就是
awk
内的NF
字段);
则可以这样:
root@orion-orion:~ last -n 5 | awk '{print $1 "\t lines: " NR "\t columns: " NF}'
root lines: 1 columns: 10
root lines: 2 columns: 10
root lines: 3 columns: 10
lines: 4 columns: 0
wtmp lines: 5 columns: 7
注意,在awk
内的NR
、NF
等变量要用大写,且不需要有美元符号$
。
接下来我们来看一看所谓的“条件类型”。
awk
的逻辑运算字符
既然要用到“条件”的类别,那么自然就需要一些逻辑运算,如下所示:
运算单元 |
代表意义 |
> |
大于 |
< |
小于 |
>= |
大于或等于 |
<= |
小于或等于 |
== |
等于 |
!= |
不等于 |
注意,逻辑运算即所谓的大于、小于等于等判断式上面,习惯上用==
而不是=
来表示,=
符号在awk
操作这里留给了变量赋值用。
我们来看下面一个例子。比如在/etc/passwd
中是以冒号:
来作为字段的分隔,该文件中第一字段为账号,第三字段为UID
。如下所示:
root@orion-orion:~ cat /etc/passwd | less
root:x:0:0:root:/root:/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
dnsmasq:x:115:65534:dnsmasq,,,:/var/lib/misc:/bin/false
那假设我要查看第三栏小于10的数据,并且仅列出账号与第三列,那么可以这样做:
root@orion-orion:~ cat /etc/passwd | awk '{FS=":"} $3 < 10 {print $1 "\t " $3}'
root:x:0:0:root:/root:/bin/zsh
daemon 1
bin 2
...
诶,不过怎么第一行没有正确地显示出来?这是因为我们在读入第一行的时候,那些变量$1
、$2
等等默认还是以空格键
做为分割,所以虽然我们定义了FS=":"
,但却仅能在第二行后才开始生效。那怎么办呢?我们可以预先设置awk
的变量,利用BEGIN
这个关键词,这样做:
root@orion-orion:~ cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}'
root 0
daemon 1
bin 2
...
接下来我们来看如何用awk
来完成计算功能。
假设我们有一个薪资数据表文件为pay.txt
,内容如下:
root@orion-orion:~ cat pay.txt
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
如何来计算每个人1st
、2nd
、3th
的总额呢?而且我们还需要格式化输出。我们可以这样考虑:
- 第一行只是表头,所以第一行不进行求和而仅需要对表头进行打印(也即
NR==1
时处理)。
- 第二行以后进行求和(
NR>=2
以后处理)。
root@orion-orion:~ cat pay.txt | \
awk 'NR == 1 {printf "%10s %10s %10s %10s %10s\n", $1, $2, $3, $4, "Total"} \
NR >= 2 {total = $2 + $3 + $4; \
printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'
Name 1st 2nd 3th Total
VBird 23000 24000 25000 72000.00
DMTsai 21000 20000 23000 64000.00
Bird2 43000 42000 41000 126000.00
上面的例子有几个重要事项应该要先说明:
awk
的命令间隔:所有awk
的操作,亦即在{}
里的操作,如果有需要多个命令辅助时,可利用分号;
间隔。
- 逻辑运算中,如果是“等于”的情况,请务必使用
==
;
- 格式化输出时,在printf的格式设置中,务必加上
\n
,才能分行(这里注意可以和Python的print
函数和shell的echo
函数做对比,此二者自带换行);
- 与bash shell中的变量不同,
awk
中的变量可以直接使用,不需要加上$
符号。
另外,awk
的操作内{}
也是支持if( 条件 )
的,比如上面的命令也可以写为:
root@orion-orion:~ cat pay.txt | \
awk '{if (NR == 1) printf "%10s %10s %10s %10s %10s\n", $1, $2, $3, $4, "Total"}