十一,awk”三元运算”与”打印奇偶行”

  参考:http://www.zsythink.net/archives/2159

  三元运算

  还记的我们在学习awk的 "if..else" 结构时,举的例子吗?我们来回顾一下。

  在centos7中,我们可以判断用户的UID是否小于1000,如果用户的UID大于1000,则用户为普通用户,如果用户的UID小于1000,则用户为系统用户。

  所以,我们可以通过awk的 "if...else结构",判断用户的UID范围,从而判断出用户属于哪种用户类型,示例如下

[root@node1 ~]# awk -F'[:]' '{if($3<1000){usertype="系统用户"}else{usertype="普通用户"};{print $1,usertype}}' /etc/passwd
root 系统用户
bin 系统用户
daemon 系统用户
adm 系统用户
lp 系统用户
sync 系统用户
shutdown 系统用户
halt 系统用户
mail 系统用户
operator 系统用户
games 系统用户
ftp 系统用户
nobody 系统用户
systemd-network 系统用户
dbus 系统用户
polkitd 系统用户
sshd 系统用户
postfix 系统用户
chrony 系统用户
zabbix 系统用户
rpc 系统用户
rpcuser 系统用户
nfsnobody 普通用户
ntp 系统用户
libstoragemgmt 系统用户
ceph 系统用户
apache 系统用户
jack 普通用户
owen 普通用户
tss 系统用户
liuym 普通用户
tcpdump 系统用户
zsy1 普通用户
zsy3 普通用户
zsy2 普通用户

   正如上图所示,我们使用"if...else"结构,对usertype变量进行了赋值,如果用户的UID小于1000,则对usertype变量赋值为"系统用户",否则则赋值usertype变量为"普通用户",最后打印出用户名所在的列与usertype变量的值。

  其实,我们可以使用三元运算,替换上例中的"if...else"结构语句,示例如下

[root@node1 ~]# awk -F'[:]' '{usertype=$3<1000?"系统用户":"普通用户";{print $1,usertype}}' /etc/passwd
root 系统用户
bin 系统用户
daemon 系统用户
adm 系统用户
lp 系统用户
sync 系统用户
shutdown 系统用户
halt 系统用户
mail 系统用户
operator 系统用户
games 系统用户
ftp 系统用户
nobody 系统用户
systemd-network 系统用户
dbus 系统用户
polkitd 系统用户
sshd 系统用户
postfix 系统用户
chrony 系统用户
zabbix 系统用户
rpc 系统用户
rpcuser 系统用户
nfsnobody 普通用户
ntp 系统用户
libstoragemgmt 系统用户
ceph 系统用户
apache 系统用户
jack 普通用户
owen 普通用户
tss 系统用户
liuym 普通用户
tcpdump 系统用户
zsy1 普通用户
zsy3 普通用户
zsy2 普通用户

   正如上图所示,红线标注部分则使用了三元运算的语法,代替了之前"if...else"的语法,而三元运算的语法如下:

  条件 ? 结果1 : 结果2

  上述语法表示,如果条件成立,则返回结果1,如果条件不成立,则返回结果2。

  而上例中,"$3<1000"就是上述语法中的"条件","系统用户"就是上述语法中"?"后面的"结果1","普通用户"就是上述语法中":"后面的"结果2"  ,同时,在上例中我们使用usertype变量接收了三元运算后的返回值,所以,当条件成立时,usertype变量被赋值为"系统用户",当条件不成立时,usertype变量被赋值为"普通用户"。

   是不是很方便?其实,三元运算还有另外一种使用方式,示例如下

[root@node1 ~]# awk -F'[:]' '{$3<1000?a++:b++}END{print a,b}' /etc/passwd
28 7

   

  我们通过上述命令,统计出了,系统用户有42个,普通用户有7个,上图中红线标注的用法可以理解为三元运算的另一种语法。如下

  表达式1 ? 表达式2 : 表达式3

  上述语法表示,如果表达式1为真,则执行表达式2,如果表达式1为假,则执行表达式3

  而上例中,"$3<1000"即为表达式1,"a++"即为表达式2,"b++"即为表达式3

  也就是说,当每遇到一个UID小于500的用户,我就对变量a加1,否则我就对变量b加1,从而算出了系统用户与普通用户的数量,最后再END模式中输出了变量a与变量b的值。

  

  打印奇偶行

  如果我们想要使用awk打印文本中的奇数行或者偶数行,则是非常简单的。

  打印奇数行

  使用行号除以2取余数如果为1则打印整行

[root@node1 ~]# awk '{if(NR%2==1) print $0}' test12
第 1 行
第 3 行
第 5 行
第 7 行
第 9 行
第 11 行

   同理打印偶数行

[root@node1 ~]# awk '{if(NR%2==0) print $0}' test12
第 2 行
第 4 行
第 6 行
第 8 行
第 10 行

   还有更简洁的方法打印奇数行和偶数行

[root@node1 ~]# awk '(i=!i)' test12
第 1 行
第 3 行
第 5 行
第 7 行
第 9 行
第 11 行
[root@node1 ~]# awk '!(i=!i)' test12
第 2 行
第 4 行
第 6 行
第 8 行
第 10 行

   正如上图所示,test12文件中有11行文本,我们可以使用非常简洁的awk命令,打印出了奇数行或者偶数行。

   但是如果我们想要彻底搞明白原理,则需要搞明白如下两个知识点(后面会有更详细的解释)

  1、在awk中,如果省略了模式对应的动作,当前行满足模式时,默认动作为打印整行,即{print $0}。

  2、在awk中,0或者空字符串表示"假",非0值或者非空字符串表示"真"

  

  上述两个知识点是什么意思呢?我们慢慢聊。

  在之前介绍awk模式的文章中提及过,模式可以理解为条件,如果当前行能与模式匹配,则会执行对应的动作。示例如下

[root@node1 ~]# awk '/1/{print $0}' test12
第 1 行
第 10 行
第 11 行
[root@node1 ~]# awk '$2>10{print $0}' test12
第 11 行
[root@node1 ~]# 

   

  上图中的两个命令均使用到了模式

  第一个命令表示如果当前行中包含字符"1",则执行对应的动作,而对应的动作就是打印整行。

  第二个命令表示如果test12文本中文本行的第二列的值如果大于10,则执行对应的动作,而对应的动作就是打印整行。

[root@node1 ~]# awk '/1/' test12
第 1 行
第 10 行
第 11 行
[root@node1 ~]# awk '$2>10' test12
第 11 行
[root@node1 ~]# 

   我们发现,当使用了模式时,如果省略了对应的动作,会默认的输出整行。

  也就是说,当使用了模式时,如果省略了模式对应的动作,默认动作为"{print $0}"

  当然,"空模式"与"BEGIN/END模式"除外。

  

  这就是第1个知识点的含义,我想你应该明白了,那么我们来聊聊第2个知识点。

  在awk中,0或者空字符串表示"假",非0值或者非空字符串表示"真",什么意思呢?我们还是可以从模式说起,"模式"可以理解为"条件",当条件成立,则为真,当条件不成立,则为假,所以,当模式为真时,则会执行对应的动作,当模式为假时,则不会执行对应的动作。

  那么,我们能不能直接把模式替换为"真"或者"假"呢?我们来试试。

 

   

  上例中,命令1使用了"空模式",也就是说,每一行都满足模式,每一行经过"空模式"匹配以后结果都是"真",所以每一行都会执行对应的动作。

  命令2中,原来"模式的位置"被替换为了数字"1",我们可以把数字"1"理解成一种模式匹配后的结果,而1是非零值,刚才说过,在awk中非零值表示真,所以,"1"表示"真",  换句话说就是模式的匹配结果为真,模式成立则会执行对应的动作,而命令2中,对应的动作为打印整行。

  命令3 与 命令2 同理,在命令3中,  数字"2"为非零值,表示真,可以理解为:模式的匹配结果为真,则会执行对应的动作,聪明如你一定想到了,数值"2"可以换做任何非0值或者非空字符串。

  命令4中,数字"2"为非零值,表示模式为真,而之前说过,当使用模式时,可以省略动作,当使用模式并省略动作时,默认动作为打印整行,所以,命令4表示打印所有行,因为每一行的模式都为真。

  命令5与命令6同理,在awk中,数字"0"与空字符串表示假,当模式为假时,不会执行对应的动作,而当存在模式并省略动作时,默认动作为打印整行,但是由于模式为假,所以对应的动作并未执行。

  其实,我们还能对真与假进行取反,非真即为假,非假即为真,示例如下。

[root@node1 ~]# awk '0' test3
[root@node1 ~]# awk '!0' test3
hey
heey
heeey
heeeey
[root@node1 ~]# 

   如果你已经看懂了上面的例子,那么,我们再来延伸一下。

  你猜猜,如下示例会输出什么?

[root@node1 ~]# awk 'i=1' test3

   没错,聪明如你一定想到了,上例中,其实是使用了awk的变量,将变量 i  赋值为1,当 i=1 以后,i为非零值,表示为真,我们可以认为这是一种模式匹配后的结果,当模式为真时,同时省略了对应动作时,默认动作为打印整行,所以上例会输出test3中的所有行。

[root@node1 ~]# awk 'i=1' test3
hey
heey
heeey
heeeey
[root@node1 ~]# awk 'i=5' test3
hey
heey
heeey
heeeey
[root@node1 ~]# awk 'i="a"' test3
hey
heey
heeey
heeeey
[root@node1 ~]# awk 'i=a' test3
[root@node1 ~]# awk 'i=0' test3
[root@node1 ~]# awk 'i=""' test3

   理解完上述示例以后,我们再回过头来,看看之前打印奇数行的示例,你可能就会明白了。

[root@node1 ~]# awk 'i=!i' test12
第 1 行
第 3 行
第 5 行
第 7 行
第 9 行
第 11 行
[root@node1 ~]# 

   当awk开始处理第一行时,变量 i 被初始化,变量 i 在被初始化时,值为"空",而awk中,数字0或者"空字符串"表示假,所以可以认为模式为假,但是 i 直接取反了,对假取反后的值为真,将取反后的值又赋值给了变量i,此刻,变量i的值为真,所以当awk处理第一行文本时,变量i的值被赋值为真,模式成立则需要执行对应的动作,而上例中又省略了动作,所以默认动作为"{print $0}",所以,第一行被整行打印了。

  当第一行文本处理完毕后,awk开始处理第二行文本,此时,i 为真,但是取反后,i 为假,所以第二行没有被输出,依次类推,最终只打印了奇数行。

  为了能够更加直观的看到上述过程,我们将i的值打印出来,通过如下动作,能够打印出处理每一行时,i 对应的值。

[root@node1 ~]# awk '{i=!i;print i}' test12
1
0
1
0
1
0
1
0
1
0
1

   当然,聪明如你,我就不用再解释打印偶数行的原理了,我想你应该已经能够举一反三了。

 

posted @ 2020-09-22 09:51  minseo  阅读(363)  评论(0编辑  收藏  举报