x除以1.1等于乘以0.93等于8.1怎么算

awk其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。实际上 AWK 的确拥有自己的语言: AWK 程序设计语言 , 三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。

awk是行处理器,相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来格式化文本信息。awk处理过程是依次对每一行进行处理,然后输出。

awk逐行处理文本,按照指定的分隔符,将行分割为多个字段,如果没有指定分隔符,默认以空格为分隔符,每个字段按照顺序,发呢别对应到awk的内置变量中,比如,分割完后的第一个字段为$1,第二个字段为 $2,依此内推,用$0表示当前处理的整行。
$0表示显示整行 ,$NF表示当前行分割后的最后一列($0$NF均为内置变量)。
注意,$NFNF要表达的意思是不一样的,对于awk来说,$NF表示最后一个字段,NF表示当前行被分隔符切开以后,一共有几个字段。

也就是说,假如一行文本被空格分成了7段,那么NF的值就是7,$NF的值就是$7, 而$7表示当前行的第7个字段,也就是最后一列,那么每行的倒数第二列可以写为$(NF-1)

先不适用选项、模式,用一个最简单的action来认识awk。

上图用了一个命令把testd文件中的内容打印了出来。
下面来操作一个类似的场景。

大家都知道df命令可以用来显示文件系统的磁盘使用情况统计。
由上面简介可知,$5为分割后的第5个字段,即磁盘使用率。
使用|管道符将df的标准输出作为awk的标准输入,于是这条命令将磁盘的使用率给列出来了。

我们也可以一次输出多列,使用逗号隔开要输出的多个列,如下,一次性输出第一列和第二列

同理,也可以一次性输出多个指定的列,如下图

我们发现,第一行并没有第5列,所以并没有输出任何文本,而第二行有第五列,所以输出了。

除了输出文本中的列,我们还能够添加自己的字段,将自己的字段与文件中的列结合起来,如下做法,都是可以的。

从上述实验中可以看出,awk可以灵活的将我们指定的字符与每一列进行拼接,或者把指定的字符当做一个新列插入到原来的列中,也就是awk格式化文本能力的体现。

但是要注意,$1这种内置变量的外侧不能加入双引号,否则$1会被当做文本输出,示例如下

我们也可以输出整行,比如,如下两种写法都表示输出整行。

BEGIN 模式指定了处理文本之前需要执行的操作,END 模式指定了处理完所有行之后所需要执行的操作。

从BEGIN模式开始,示例如下

上述写法表示,在开始处理test文件中的文本之前,先执行打印动作,输出的内容为"aaa","bbb".

也就是说,上述示例中,虽然指定了test文件作为输入源,但是在开始处理test文本之前,需要先执行BEGIN模式指定的"打印"操作

既然还没有开始逐行处理test文件中的文本,那么是不是根本就不需要指定test文件呢,我们来试试。

经过实验发现,还真是,我们并没有给定任何输入来源,awk就直接输出信息了,因为,BEGIN模式表示,在处理指定的文本之前,需要先执行BEGIN模式中指定的动作,而上述示例没有给定任何输入源,但是awk还是会先执行BEGIN模式指定的"打印"动作,打印完成后,发现并没有文本可以处理,于是就只完成了"打印 aaa bbb"的操作。

这个时候,如果我们想要awk先执行BEGIN模式指定的动作,再根据执我们自定义的动作去操作文本,该怎么办呢?示例如下

上图中,蓝色标注的部分表示BEGIN模式指定的动作,这部分动作需要在处理指定的文本之前执行,所以,上图中先打印出了"aaa bbb",当BEGIN模式对应的动作完成后,在使用后面的动作处理对应的文本,即打印test文件中的第一列与第二列,这样解释应该比较清楚了吧。

看完上述示例,似乎更加容易理解BEGIN模式是什么意思了,BEGIN模式的作用就是,在开始逐行处理文本之前,先执行BEGIN模式所指定的动作。以此类推,END模式的作用就一目了然了,举例如下。

聪明如你一定明白了,END模式就是在处理完所有的指定的文本之后,需要指定的动作。

那么,我们可以结合BEGIN模式和END模式一起使用。示例如下

上述示例中返回的结果有没有很像一张"报表",有"表头" 、"表内容"、 "表尾"。

输入分隔符,英文原文为field separator,此处简称为FS。
输入分割符,默认是空白字符(即空格),awk默认以空白字符为分隔符对每一行进行分割。

awk将每行分割后,输出在屏幕上的时候,以什么字符作为分隔符,awk默认的输出分割符也是空格。

输入分隔符比较容易理解,当awk逐行处理文本的时候,以输入分隔符为准,将文本切成多个片段,默认使用空格,但是,如果一段文字中没有空格,我们可以指定以特定的文字或符号作为输入分割符,比如下图中的例子,我们指定使用"#"作为输入分隔符。

上图中,我们使用了-F 选项,指定了使用#号作为输入分隔符,于是,awk将每一行都通过#号为我们分割了。

除了使用 -F 选项指定输入分隔符,还能够通过设置内部变量的方式,指定awk的输入分隔符,awk内置变量FS可以用于指定输入分隔符,但是在使用变量时,需要使用-v选项,用于指定对应的变量,比如 -v FS='#',如下图:

其实不管是通过-F选项,还是通过FS这个内置变量,目的都是设置指定的输入分隔符,达到的效果是相同的,下面会单独对awk的变量进行总结,如果你不理解这些变量,没有关系,后面自然会明白。

而此处,我们使用了awk中的一个选项,就是-F,还记得我们之前总结的awk的使用语法吗。

我们说过,awk的语法如下

而-F,就是options的一种,用于指定输入分隔符。

-v也是options的一种,用于设置变量的值。

再结合之前的文章,我们已经将options 、pattern 、action都简单的应用了一遍,好了,我们已经"会用"awk了。

那么什么是输出分隔符呢?聪明的你应该已经发现了,当awk为我们输出每一列的时候,会使用空格隔开每一列,其实,这个空格,就是awk的默认的输出分隔符,下图中红线标注的空格部分,就是awk的默认的输出分隔符。

输出分割符的意思就是:当我们要对处理完的文本进行输出的时候,以什么文本或符号作为分隔符。

我们可以使用awk的内置变量OFS来设定awk的输出分隔符,当然,使用变量的时候要配合使用-v选项,示例如下

现在,我们可以同时指定输入分隔符和输出分割符了,示例如下

我们刚才解释了awk的输出分隔符,如果,在输出的时候,我们想要让两列合并在一起显示,不使用输出分隔符分开显示,该怎么做呢?如下图所示,蓝线之上使用默认的输出分隔符进行了分隔,而蓝线之下的两种方法均未使用输出分隔符进行分隔,而是将两列合在一起显示了。

细心如你一定发现了,上图中的示例在语法上的区别就是,一个有"逗号",一个没有"逗号"。

awk '{print $1 $2}'表示每行分割后,将第一列(第一个字段)和第二列(第二个字段)连接在一起输出。

awk '{print $1,$2}'表示每行分割后,将第一列(第一个字段)和第二列(第二个字段)以输出分隔符隔开后显示。

在使用到"输入分隔符"和"输出分隔符"的时候,我们都提到了一个名词:"变量"。
对于awk来说"变量"又分为"内置变量" 和 "自定义变量" , "输入分隔符FS"和"输出分隔符OFS"都属于内置变量。
内置变量就是awk预定义好的、内置在awk内部的变量,而自定义变量就是用户定义的变量。

5.1awk常用的内置变量以及其作用

FS:输入字段分隔符, 默认为空白字符
OFS:输出字段分隔符, 默认为空白字符
RS:输入记录分隔符(输入换行符), 指定输入时的换行符
ORS:输出记录分隔符(输出换行符),输出时用指定符号代替换行符
NF:number of Field,当前行的字段的个数(即当前行被分割成了几列),字段数量
NR:行号,当前处理的文本行的行号。
FNR:各文件分别计数的行号
ARGC:命令行参数的个数
ARGV:数组,保存的是命令行所给定的各参数

上面描述到的"输入字段分隔符FS和输出字段分隔符OFS在之前的文章中已经解释过了,字段数量NF也大致说了。

RS、ORS、NR、FNR、FILENAME、ARGC、ARGV这些术语对于我们来说是新接触的,但是触类旁通,RS其实与FS类似,ORS与OFS类似,FS是字段输入分隔符,RS是行输入分隔符,OFS是字段输出分隔符,ORS是行输出分隔符,它们的原理都很相似。

如下图所示,test1文件中一共有两行文本,使用空格隔开,第1行有4列,第2行有5列

而内置变量NR表示每一行的行号,内置变量NF表示每一行中一共有几列,那么,也就是说,我们可以通过下例中的方法,得到test1文本中,每一行的行号以及每一行对应的列的数量。

或者,利用NR内置变量,先打印出行号,再打印出整行的内容,相当于为test1中的每一行都添加了行号以后再进行输出,示例如下。

好了,现在每一行的开头都有行号了,简单吧。

细心如你一定注意到了一个细节,就是在打印 $0, $1 , $2这些内置变量的时候,都有使用到"$"符号,但是在调用 NR , NF 这些内置变量的时候,就没有使用"$",如果你有点不习惯,那么可能是因为你已经习惯了使用bash的语法去使用变量,在bash中,我们在引用变量时,都会使用$符进行引用,但是在awk中,只有在引用$0$1等内置变量的值的时候才会用到"$",引用其他变量时,不管是内置变量,还是自定义变量,都不使用"$",而是直接使用变量名。

当我们使用awk同时处理多个文件,并且使用NR显示行号的时候,效果如下图。

从返回结果可以看出,awk处理多个文件的时候,如果使用NR显示行号,那么,多个文件的所有行会按照顺序进行排序。

可是,如果我们想要分别显示两个文件的行号,该怎么办呢,这个时候就会用到内置变量FNR,效果如下。

FNR内置变量的作用就是当awk处理多个文件时,分别对每个文件的行数进行计数。

RS是输入行分隔符,如果不指定,默认的"行分隔符"就是我们所理解的"回车换行"。

假设,我们不想以默认的"回车换行"作为"行分隔符",而是想使用空格作为所谓的行分隔符,也就是说,我们想让awk认为,每遇到一个空格,就换行,换句话说,我们想让awk以为每次遇到一个空格就是新的一行。那么我们该怎么做呢?示例如下。

如上图所示,我们先使用了默认的"回车换行"作为"行分隔符"输出了test1文本,这时显示文本一共有2行。

而后来,我们又指定了使用"空格"作为"行分隔符"输出test1文本,这时显示文本一共有8行。

看到了吗?当我们指定使用空格作为"行分隔符"时,在awk解析文本时,每当遇到空格,awk就认为遇到的空格是换行符,于是awk就将文本换行了,而此时人类理解的"回车换行",对于awk来说并不是所谓的换行符,所以才会出现上图中第4行的现象,即使从人类的角度去看是两行文本,但是在awk的世界观里,它就是一行。

如果你还是没有理解,那么我们换个方式描述,再来啰嗦一遍。

默认情况下,awk使用"回车换行"作为"行分隔符(换行符)",此时,人类的世界观与awk的世界观是一致的,因为我们和awk都认为,遇到回车换行,就表示当前行结束,开始新的一行。

而当我们指定了特定的"行分隔符"时,比如空格,那么当awk遇到空格时,就认为当前行结束了,新的一行开始了,此时,awk的世界观与人类的世界观已经不同,人类仍然认为"回车换行"才是新的一行,awk却认为"回车换行"并不是新的一行的开始,所以,从上图中返回的信息中,我们可以看到,人类所以为的"两行",共用了一个行号,awk认为它们就是第4行。

这就是输入行分隔符的使用方法。同理,我们来看看"输出行分隔符",理解输出行分隔符之前,请做好心理准备,最好不要以正常的思维去理解换行,才能比较容易的学明白输出行分隔符。

在理解"输出行分隔符"ORS之前,请先理解刚才描述的"输入行分隔符"RS,否则理解起来可能比较困难。

默认情况下,awk将人类眼中的"回车换行",当做"输出行分隔符",此时,awk的"世界观"与人类的"世界观"是相同的。

现在,我们改变一下awk的想法,我们让awk认为,"+++"才是真正的输出行分隔符,示例如下图

看懂了吗,我们再啰嗦的解释一遍,在没有指定输出行分隔符之前,awk跟人类的逻辑思维是一样一样的,当人类想要换行的时候,就会"另起一行"(回车换行),awk也是一样的,当它在输出文字的时候,如果想要换行,就会"另起一行"(回车换行), 可是,如果我们指定了"输出行分隔符"为"+++",那么,当awk在输出文字的时候,如果想要换行,就会"另起一行"(+++),所以,对于awk来说,它完成了"另起一行"的动作,只不过,它所认为的"另起一行"的动作就是输出"+++",而不再是原来的输出" 回车换行",所以,从人类看到的"表象上",awk并没有换行,那是因为我们还是以"回车换行"作为换行的标准,而awk已经变了,它认为,"+++"就是换行的标准。

这次明白了吧,真的明白了吗?

我们把刚才学到的"输入换行符"和"输出换行符"同时使用,看看是什么效果,示例如下。

如果你能明白awk为什么会将test1的文本输出成上图中的模样,那么你已经彻底理解了RS与ORS两个内置变量。

如果你又懵逼了,那么,从RS内置变量开始,再看一遍吧。

FILENAME这个内置变量,从字面上,就能看出是什么意思,没错,就是显示文件名,演示效果如下。

ARGC内置变量表示命令行参数的个数,什么意思呢?我们先不解释ARGC,先看看ARGV是什么。

ARGV内置变量表示的是一个数组,这个数组中保存的是命令行所给定的参数。这样解释还是很模糊,不容易理解,我们来看看示例。

上图中,我们先使用BEGIN模式,输出一个字符串"aaa",然后,传入两个文件的文件名作为参数,我们发现,BEGIN模式正常执行了打印操作,输出了"aaa"字符串 ,我们使用同样的命令,同样使用BEGIN模式,只不过,这次不只打印"aaa",还打印ARGV这个数组中的第二个元素的值。

我说已经说过,ARGV内置变量表示的是一个数组,既然是数组,就需要用上图中的下标的方式,引用对应元素的值,因为数组的索引都是从0开始的,所以,ARGV[1]表示引用ARGV数组中的第二个元素的值,从返回结果可以看出,ARGV[1]对应的值为test1,同理,我们又使用第三条命令,多打印了一个ARGV[2]的值,发现ARGV[2]对应的值为test2,这个时候,你明白ARGV内置变量的含义了吗,说白了,ARGV内置变量表示的是:所有参数组成的数组。那么细心的你一定会问了,ARGV[0]对应的是哪个参数呢,我们来打印一下。

我擦,第一个参数竟然是awk这个命令本身??太神奇了,有没有很出乎意料···

好吧,awk就是这么规定的,'pattern{ action }'并不被看做是参数,awk被看做为参数。

好了,说明了ARGV变量以后,再说ARGC变量的作用,就容易多了。

在刚才的例子中,应该有三个参数,awk、test1、test2,这三个参数作为数组的元素存放于ARGV中,现在,而ARGC则表示参数的数量,也可以理解为ARGV数组的长度。示例如下

好了,内置变量解释完了,现在我们来看看自定义变量,自定义变量,顾名思义,就是用户定义的变量,有两种方法可以自定义变量。

方法二:在program中直接定义。

我们来看一些小例子,即可明白上述两种方法。

通过方法一自定义变量。

这种方式,与设置内置变量的值的方法是一样的。

使用方法二自定义变量,直接在program中定义即可,但是注意,变量定义与动作之间需要用分号";"隔开。

当然,我们也可以一次性定义多个变量

第一种方法虽然看上去比较麻烦,但是这种方法也有自己的优势

当我们需要在awk中引用shell中的变量的时候,则可以通过方法一间接的引用。举例如下

利用awk中的printf动作,即可对文本进行格式化输出,printf动作的用法与printf命令的用法非常相似,只是有略微的不同而已,不过,我们还是从最简单的示例开始看起,首先对比一下print动作与printf动作的区别,示例如下

没错,printf动作与printf命令一样,都不会输出换行符,默认会将文本输出在一行里面。

聪明如你一定想到了,既然printf动作的用法与printf命令一样,那么,printf动作有没有printf命令中所谓的"格式替换符"呢?

必须有啊,"格式替换符"是什么我们就不再赘述了,因为在中已经详细的解释过它,那么我们来使用"格式替换符"来指定一下$1的格式,示例如下。

如果只看上图中红线标注的部分,你肯定会认为,这就是printf命令的用法,只是printf动作与printf命令在语法上唯一的不同点就是,在使用printf动作时,指定的"格式"与列$1之间需要用"逗号"隔开,而使用printf命令时,指定的格式与传入的文本不需要使用"逗号"隔开,如下图所示

其实,它们还有一些其他的不同之处,我们在使用printf命令时,当指定的格式中只有一个"格式替换符",但是传入了多个参数时,那么这多个参数可以重复的使用这一个格式替换符,示例如下

但是在awk中,我们则不能这样使用,在awk中,格式替换符的数量必须与传入的参数的数量相同,换句话说,格式替换符必须与需要格式化的参数一一对应,示例如下。

好了,这就是awk中printf动作在使用时的一些注意点。

我们来总结一下,在awk中使用printf动作时,需要注意以下3点。

1)使用printf动作输出的文本不会换行,如果需要换行,可以在对应的"格式替换符"后加入"\n"进行转义。

2)使用printf动作时,"指定的格式" 与 "被格式化的文本" 之间,需要用"逗号"隔开。

3)使用printf动作时,"格式"中的"格式替换符"必须与 "被格式化的文本" 一一对应。

好了,我们来看一些小示例,练练手。

我们可以利用格式替换符对文本中的每一列进行格式化,示例如下。

我们可以利用awk的内置变量FS,指定输入字段分隔符,然后再利用printf动作,进行格式化,示例如下。

上例完美的体现了awk的格式化能力,因为awk本身负责文本切割,printf动作负责格式化文本,双剑合璧了。

继续扩展一下,可以利用awk的begin模式,结合printf动作,输出一个像样的表格,下图中用到的"修饰符"此处不再赘述,如果不明白,参考。

"模式"这个词听上去文绉绉的,不是特别容易理解,那么我们换一种说法,我们把"模式"换成"条件",可能更容易理解,那么"条件"是什么意思呢?我们知道,awk是逐行处理文本的,也就是说,awk会先处理完当前行,再处理下一行,如果我们不指定任何"条件",awk会一行一行的处理文本中的每一行,如果我们指定了"条件",只有满足"条件"的行才会被处理,不满足"条件"的行就不会被处理。这样说是不是比刚才好理解一点了呢?这其实就是awk中的"模式"。

再啰嗦一遍,当awk进行逐行处理的时候,会把pattern(模式)作为条件,判断将要被处理的行是否满足条件,是否能跟"模式"进行匹配,如果匹配,则处理,如果不匹配,则不进行处理。

看个小例子,就能秒懂,前提是建立在之前知识的基础之上。

如下图所示,test2文件中有3行文本,第一行有4列,第二行有5列,第三行只有2列。而下图的awk命令中,就使用到了一个简单的模式。

上图中,我们使用了一个简单的"模式",换句话说,我们使用了一个简单的"条件",这个条件就是,如果被处理的行正好有5列字段,那么被处理的行则满足"条件",满足条件的行会执行相应的动作,而动作就是{print $0},即打印当前行,换句话说,就是只打印满足条件的行,条件就是这一行文本有5列(NF是内置变量,表示当前行的字段数量,如果你忘了,那么请你重新看一遍之前的文章),而上例中,只有第二行有5列,所以,只有第二行能与我们指定的"模式"相匹配,最终也就只输出了第二行。

这就是所谓的"模式",其实很简单,对吧。聪明如你,应该已经能够举一反三了,举例如下。

没错,"模式"怎样写,取决于我们想要给出什么样的限制条件。

细心如你一定发现了,上图中使用的"模式"都有一个共同点,就是上述"模式"中,都使用到了关系表达式(关系操作符),比如 ==,比如<=,比如>,当经过关系运算得出的结果为"真"时,则满足条件(表示与指定的模式匹配),满足条件,就会执行相应的动作,而上例中使用到的运算符都是常见的关系运算符,我们就不解释了,那么awk都支持哪些关系运算符呢?我们来总结一下。

我们把这种用到了"关系运算符"的"模式"称之为:"关系表达式模式"或者"关系运算符模式"。

其实,在学习"模式"之前,我们一直都在使用"模式",只是我们没有感觉到而已,为什么这么说呢?听完下面的解释,你就会明白。

上图中的命令1指定了"模式",而且这种"模式"是"关系表达式模式",如果当前行的字段数量等于5,模式被匹配,对应的行被打印。

上图中的命令2貌似没有使用任何"模式",所以,每一行都执行了指定的动作,即每一行都被输出了,其实,这种没有被指定任何"模式"的情况,也是一种"模式",我们称这种情况为"空模式","空模式"会匹配文本中的每一行,所以,每一行都满足"条件",所以,每一行都会执行相应的动作。

现在,我们不仅懂得了什么是awk的"模式(Pattern)",而且还掌握了两种"模式",空模式和关系运算模式。

不对,我们似乎遗忘了什么 ,我们还用过BEGIN模式和END模式,我们来回顾一下吧。

BEGIN模式,表示在开始处理文本之前,需要执行的操作。

END模式,表示将所有行都处理完毕后,需要执行的操作。

还记得我们在第一篇awk博文中使用到的例子吗,温故知新,回过头看,会有新发现

上图中的示例用到了BEGIN模式,空模式,END模式。

我们知道,在Linux中,/etc/passwd文件中存放了用户信息,那么假设 ,我们想要从/etc/passwd文件中找出"用户名以zsy开头"的用户,我们该怎么办呢?

没错,我们可以使用grep命令,配合正则表达式,找出对应的信息,示例如下。

注:如果你还不了解grep命令和正则表达式,请参考博客中的文章,此处不再赘述。

如上例所示,我们通过grep命令,配合正则表达式,找出了我们需要的信息,那么,使用awk命令,能否完成上述需求呢?

答案是肯定的,那么我们一起来看看,使用awk命令,怎样从/etc/passwd文件中找出用户名以zsy开头的用户,示例如下

聪明如你一定看出来了,不管是使用grep命令,还是使用awk命令,都使用了相同的正则表达式"^zsy"

唯一的区别就是,在grep命令中,直接使用了正则表达式,而在awk命令中,正则表达式被放入了两个斜线中。

这样说可能不容易理解,看图说话似乎更加容易理解。

上图中,awk命令在使用正则表达式时,将正则表达式放入了"/ /"中。

其实,这就是我们今天要介绍的"正则模式",在使用"正则模式"时,文本行如果能够被正则表达式匹配到,就会执行对应的动作,如果没有被正则匹配到,则不会执行对应的动作,而上例中,对应的动作就是{print $0},也就是打印整行,所以,上例中的grep命令与awk命令所实现的效果是完全相同的,那么你可能会问,既然效果完全相同,为什么还要使用awk呢?似乎grep更加简单一些,没错,上例中,grep是更加简单一些,但是不要忘了,awk有自己的优势,就是格式化能力,那么,我们换一个场景,可能使用awk就会更加实用了,示例如下。

猛然一看,上例似乎非常复杂,但是如果你已经掌握了前文中的知识,那么你一定能够看明白,上例中蓝线标注的部分使用了BEGIN模式,并且格式化输出了一行文本作为"表头",上例中红线标注的部分使用了正则模式,并且格式化输出了/etc/passwd文件中的第一列与第三列(用户名字段与用户ID字段),上例中,只使用了awk一条命令就完成了如下多项工作。

1、从/etc/passwd文件中找出符合条件的行(用户名以zsy开头的用户)。
2、找出符合条件的文本行以后,以":"作为分隔符,将文本行分段。
3、取出我们需要的字段,格式化输出。
4、结合BEGIN模式,输出一个格式化以后的文本,提高可读性。

因为我们在处理文本时,往往需要用到正则表达式,所以,awk的正则模式应该会经常用到。

但是需要注意,在使用正则模式时,如果正则中包含"/",则需要进行转义,这样说可能不容易理解,我们来看个例子。

仍然使用/etc/passwd进行测试,我们知道,/etc/passwd中保存了用户信息,其中每行的最后一个字段为用户使用的登录shell,假设,我们想要从passwd文件中找出使用/bin/bash作为登录shell的用户,我们该怎么办呢?

没错,我们可以使用grep命令,配合正则表达式完成我们的需求,示例如下。

如上图所示,使用"/bin/bash"作为登录shell的用户都被我们找了出来。

同理,我们使用awk命令,同样能够实现与grep相同的效果。

于是,按照套路,你可能会尝试如下命令。

正如上图所示,按照套路,我们将正则部分放入了两个斜线中,但是运行命令时却报错。

这是因为正则中包含"/",而当使用正则模式时,又需要把正则放入到两个"/"中。

所以,我们需要对正则中的"/"进行转义,转义后即可正常运行命令,示例如下

如上图所示,经过转义后,awk命令即可正常的匹配到符合正则条件的行,并执行了相应的动作。

除此之外,还要注意以下两点

1、当在awk命令中使用正则模式时,使用到的正则用法属于"扩展正则表达式"(如果不理解,请参考博客中的"正则表达式"系列文章)。

2、当使用 {x,y} 这种次数匹配的正则表达式时,需要配合--posix选项或者--re-interval选项。

上例中,正则模式中的正则表达式为"he{2,3}y",此表达式表示"hey"中的字母e最少需要连续出现2次,最多只能连续出现3次,才能被正则表达式匹配到,但是正如上图所示,没有使用--posix选项或者--re-interval选项时,awk无法根据正则表达式对文本进行处理,因为上例的正则中包含类似"{x,y}"这样的次数匹配字符,所以,在使用正则模式时,如果对应的正则表达式中包含类似"{x,y}"这样的次数匹配字符,则需要使用--posix选项或者--re-interval选项。

好了,正则模式我们已经说明白了,赶快动手试试吧。

其实,只要理解了正则模式,再理解行范围模式,就容易多了。

在介绍行范围模式之前,先来思考一个小问题,有一个文本文件,文件内容如下。

如上图所示,Lee这个名字出现了两次,第一次出现是在第2行,Kevin这个名字也出现了两次,第一次出现是在第5行。

假设我想从上述文本中找出,从Lee第一次出现的行,到Kevin第一次出现的行之间的所有行,我该怎么办呢?

使用awk的行范围模式,即可完成上述要求,示例如下。

我们来解释一下,上例中的行范围模式的语法是什么意思。

我们可以把上述行范围模式的语法与正则模式的语法对比着理解,可能更加方便我们理解。

上图中第一种语法是正则模式的语法,表示被正则表达式匹配到的行,将会执行对应的动作。

上图中第二种语法是行范围模式的语法,它表示,从被正则1匹配到的行开始,到被正则2匹配到的行结束,之间的所有行都会执行对应的动作,所以,这种模式被称为行范围模式,因为它对应的是一个范围以内的所有行,但是需要注意的是,在行范围模式中,不管是正则1,还是正则2,都以第一次匹配到的行为准,就像上述示例中,即使Lee在第2行与第3行中都出现了,但是由于正则1先匹配到第2行中的lee,所以,最终打印出的内容从第2行开始,即使Kevin在第5行与第7行中都出现了,但是由于Kevin第一次出现在第5行,所以最终打印出的内容到第5行结束,也就是说,最终打印出了第2行到第5行以内的所有行。

但是,你可能会有这样的需求,你不想依靠正则表达式去匹配行的特征,你只是想单纯的打印出从X行到Y行之间的所有行。

比如,我们有一个文本文件,这个文件中一共有7行文本,你想要打印出从第3行到第6行之间的所有行,该怎么做呢?

其实,使用之前学习到的"关系运算符模式",即可满足我们的需求,示例如下。

上图中,NR为awk的内置变量,表示行号,"NR>=3 && NR<=6"表示行号大于等于3,并且行号小于等于6时,执行对应的动作,而对应的动作就是打印整行,所以,上述命令表示打印出文本中从第3行到第6行之间的所有行。

比如,我想要从如下文本中找出,网卡1的IP地址在192.168.0.0/16网段内的主机,该怎么办呢?

我们可以使用如下命令,利用关系运算符与正则模式,达到我们的目的。

上述示例中,$2为awk的内置变量,表示文本中的第2列,"$2~/正则/"表示文本中的第2列如果与正则匹配,则执行对应的动作,对应的动作为"{print $1,$2}",表示打印文本中的第1列与第2列,上例中的正则表达式我就不再赘述了,如果你还不太了解正则表达式,可以参考博客中的正则系列文章。

是不是很简单?我想你应该明白了。

到目前为止,我们已经认识了awk的模式,模式可以总结为如下5种。

上面介绍过了awk的选项、模式以及动作,这里详细总结一下动作action。

如上图所示,红线标注部分就是awk命令中的"动作"。

其实,我们可以把上述"动作"分解,拆开成两部分去理解,如下图所示。

上图中,我们将动作拆分成了两个部分。

红线标注为第一部分:最外侧的括号,即"{ }"。

在之前的示例中,我们一直把上图中的两个部分当做一个整理去理解,但是现在,我们要把它们分开去理解。

其实,这两个部分都可以被称之为"动作",只不过它们是不同"类型"的动作而已。

"print"属于"输出语句"类型的动作,顾名思义,"输出语句"类型的动作的作用就是输出、打印信息,没错,"print"与"printf"都属于"输出语句"类型的动作。

"{ }"其实也可以被称之为"动作",只不过,"{ }"属于"组合语句"类型的动作,顾名思义,"组合语句"类型的动作的作用就是将多个代码组合成代码块。

这样说可能不容易理解,我们来看个小示例,就容易理解了,示例如下。

如上图所示,我们使用了两个大括号"{ }",它们属于"组合语句"类型的动,它们分别将两个print括住,表示这两个print动作分别作为两个独立的个体,如下图所示。

也就是说,我们可以这样理解,上图中一共有4个"动作",两对大括号,两个print,但是上图中,每个大括号中只有一个动作,而我们说过,"组合语句"的作用是将多个代码或多个动作组合成代码块,组合后的代码块被当做一个整体,那么,我们能不能把上图中的两个print动作组合成一个整体呢?

awk从放弃到入门(7):awk动作总结之一

如上图所示,我们只使用了一个大括号,将两个print动作组合成了一个整体,但是细心如你一定发现来了,当我们把多个动作(多段代码)组合成一个代码块的时候,每段动作(每段代码)之间需要用分号";"隔开,如下图所示。

好了,我想你应该明白了,除了print这种"输出语句"能够被称之为动作以外,像"{ }"这种"组合语句"也能被称之为动作,只不过它们的类型不同,功能也不同。

那么,除了"输出语句"与"组合语句"以外,还有其他种类的动作吗?

必须的,我们现在就来认识另一种动作,它就是"控制语句"。

不过,"控制语句"又有很多种,不过不用怕,我们慢慢来,一个一个聊,先来认识一种简单的"控制语句",它就是"条件判断"。

如果你有过任何一种编程语言的开发经验,你都会非常容易理解"条件判断",条件判断无非就是条件成立,则执行对应的代码,条件不成立,则不执行对应的命令,没错,在编程语言中,通常使用如下语法结构进行条件判断,也就是编程语法中的 if 条件判断语句。

在awk中,我们同样可以使用if这种语法进行条件判断,只不过,上例中的语法结构是由"多行"组成,而在命令行中使用awk时,我们可以将上例中的"多行"语句写在"一行"中,示例如下。

上图中红线标注的部分即为"条件判断"类型的语法,我们把红线标注的部分单独取出来,来描述一下。

"if(NR == 1){ print $0 }"表示行号为1是满足条件,条件满足时,打印整行,换句话说就是只打印第一行。

你可能会纠结,为什么最外侧还需要有一层大括号呢?如下图所示。

告诉你原因,原因就是·····

没有为什么,就是要这样写,否则会报错。

如果你非要一个理由,那么我们可以这样理解,所有动作的最外侧必须用"{ }"括起。

你可能还是会纠结,if语句的语法结构中也包含大括号啊,那么它属于"组合语句"吗?如下图所示

虽然它的语法结构中也包含大括号,但是我们仍然把if语句称之为"控制语句",或者我们可以这样理解,"控制语句"中包含"组合语句"。

if语句中的大括号中,也可以执行多个动作,把多个代码段当做一个整体,也就是说,如果if所对应的条件成立,则执行if的大括号中的所有命令,示例如下。

上例表示,如果行号为1,则满足条件,就会执行if对应的大括号中的所有代码,而大括号中,有两个print动作,当条件成立时,这两个print动作都会被执行,当条件不成立时,这两个动作都不会执行。

上例中,"if"对应的大括号中有多条语句,所以"if"语法中的大括号不能省略,但是,如果"if"对应的大括号中只有一条命令,那么"if"对应的大括号则可以省略,示例如下。

如上图所示,当"if"对应的大括号中只有一条命令时,对应的大括号可以省略,但是需要注意,如果条件成立之后,需要执行多条语句,那么"if"对应的大括号则不能省略。

还记得我们在前文中使用到的"模式"吗?示例如下

没错,上图中的用法为awk的"模式"的用法,而我们今天所介绍的用法为awk的"动作"的用法,虽然两者在语法上有所区别,但是达到的目的相同的。

我们知道,/etc/passwd文件中的第3列存放了用户的ID,在centos6中,用户ID小于500的用户都属于系统用户,用户ID大于500的用户都属于普通用户。

所以,我们可以以500为分界线,根据用户ID判断用户是属于系统用户还是普通用户,centos7中以1000为分界线,此处用于示例的系统为centos6,所以以500作为分界线。

我们可以通过一条awk命令,判断出/etc/passwd文件中的哪些用户属于系统用户,哪些用户属于普通用户,示例如下。

上图中,就用到了"if...else..."语法,如上图所示,$3对应了passwd文件中的第三列,即用户ID,如果用户ID小于500,则输出$1,即passwd文件中的第一列,也就是用户名,并且输出"系统用户"字样,否则,则执行else中的命令,即打印用户名并输出"普通用户"字样,但是上例中,为了方便演示,我们并没有对输出的文本进行格式化,你也可以结合之前的知识,进行格式化。

好了,再来看一个"if...else if...else"这样的例子,其他它们都差不多,示例如下:

上例中,我们使用了"关系表达式"模式,同时,在动作中,使用了"if...else if...else"这样的"控制语句",只要前文中的知识都掌握了,那么看懂上述示例,应该是没有任何问题的。

因为我们还没有介绍过数组,所以此处只演示上述语法中的格式1的用法。

上例中,我们使用了BEGIN模式,BEGIN模式对应的动作中,包含了for循环语句,看到这里,是不是感觉与其他语言中的for循环完全没有区别嘛?只不过,上例中的for循环语句都写在了一行中而已。

再来看看while循环的具体使用,为了方便演示,仍然使用BEGIN模式,示例如下。

当while对应的条件满足时,则执行对应的语句,语句执行完成后,对条件进行修改。

同理,do...while的示例如下,它与while循环的不同之处在于,while循环只有当满足条件时才会执行对应语句,而do...while循环则是无论是否满足条件,都会先执行一遍do对应的代码,然后再判断是否满足while中对应的条件,满足条件,则执行do对应的代码,如果不满足条件,则不再执行do对应的代码。

正如上图所示,无论是否满足while中的条件,都会先执行一遍do对应的代码。

那么,说到循环,就不能不说说与循环有关的跳出语句。

没错,与其他编程语言中一样,在awk中,同样可以使用break与continue跳出循环。

break的作用:跳出"整个"循环

示例如下,先看看continue的示例

由于在for循环中添加了条件判断,所以当 i 的值为 3 时,跳过了"当前本次"循环,没有执行当前本次循环需要执行的动作,所以上例中,数字"3"并没有被打印出来。

当然,如果你想"结束"的更加"彻底",可以使用break结束循环,示例如下。

如上图所示,break结束的更加彻底,当使用break时,整个循环都将被结束,循环中的动作将不会再被执行。

当然,如果你经常编写过shell脚本,你可能会问,awk中有类似exit的语句吗?必须有啊,在shell中,exit命令表示退出当前脚本,在awk中,它的含义也是类似的,它表示不再执行awk命令,相当于退出了当前的awk命令,示例如下。

如上图所示,上图中第一条命令中,执行了多个动作(多条语句),上图中的第二条命令中,也执行了多个动作,但是当在awk中执行了exit语句以后,之后的所有动作都不会再被执行,相当于退出了整个awk命令。

其实,这样描述exit的作用并不准确,因为,当在awk中使用了END模式时,exit的作用并不是退出整个awk命令,而是直接执行END模式中的动作,示例如下。

如上图所示,当awk中使用了END模式时,如果执行了exit语句,那么exit语句之后的所有动作都将不会再被执行,END模式中的动作除外。

换句话说就是,当执行了exit语句后,如果使用了END模式,将直接执行END模式中的动作,其他动作将不会被执行,如果没有使用END模式,当执行了exit语句后,将直接退出整个awk命令。

在awk中,除了能够使用"exit命令"结束"整个awk",还能够使用"next命令"结束"当前行",什么意思呢?我们慢慢聊。

在前文中,我们提到过,awk是逐行对文本进行处理的,也就是说,awk会处理完当前行,再继续处理下一行,那么,当awk需要处理某一行文本的时候,我们能不能够告诉awk :"不用处理这一行了,直接从下一行开始处理就行了"。

没错,使用next命令即可让awk直接从下一行开始处理,换句话说就是,next命令可以促使awk不对当前行执行对应的动作,而是直接处理下一行,示例如下。

其实,next与continue有些类似,只是,continue是针对"循环"而言的,continue的作用是结束"本次循环",而next是针对"逐行处理"而言的,next的作用是结束"对当前行的处理",从而直接处理"下一行",其实,awk的"逐行处理"也可以理解成为一种"循环",因为awk一直在"循环"处理着"每一行",不是吗?

前文中提及过,awk其实可以算作一门脚本语言,因为它包含了一个脚本语言的各种语法结构,比如条件判断语句,比如循环语句,那么,awk中能否使用"数组"呢?必须能啊,今天我们就来聊聊awk中的数组。

如果你有过任何一种编程语言的使用经验,那么你一定知道,我们可以通过数组的下标(或者称索引),引用数组中的元素,其他语言中,数组的下标通常由0开始,也就是说,如果想要引用数组中的第1个元素,则需要引用对应的下标"[0]",awk中的数组也是通过引用下标的方法,获取数组中的元素的,但是在awk中,数组元素的下标默认从1开始,但是为了兼容你的使用习惯,我们也可以从0开始设置下标,此处不用纠结,到后面自然会明白,我们先来看一个最简单的示例。

在其他语言中,你可能会习惯性的先"声明"一个数组,在awk中,则不用这样,直接为数组中的元素赋值即可,示例如下。

如上图所示,为了方便示例,上例中使用了BEGIN模式,在BEGIN模式中,存在一个名为"葫芦娃"(拼音)的数组,我们在这个数组中放置了3个元素,第1个元素为"大娃",第2个元素为"二娃",第3个元素为"三娃",如果我们想要引用数组中第二个元素的值,只要引用下标为1的元素即可,正如上图所示,我们使用下标"[1]",获得了huluwa这个数组中第二个元素的值,即"二娃"。

当然,如果你想要看到更多的"葫芦娃",可以在数组里面放置更多的元素。

如上图所示,由于命令太长,可读性可能会降低,为了在编写时提高命令的可读性,我们可以使用Linux命令行的"换行符"进行换行,Linux中,命令行的换行符为反斜杠"",上述命令换行后,如下。

可以看到,目前葫芦娃数组中已经存在6个葫芦娃了,我们可以获取到我们想要的葫芦娃,换句话说,我们可以通过数组的下标,获取到任何一个元素的值。

我们知道,在动画中,六娃的超能力是"隐身",所以六娃也叫"隐身娃",那么,我们就把上述数组中的第5个元素的值设置为"空字符串"吧,用空字符串表示六娃已经"隐身"了,示例如下。

如上图所示,上例数组中的第5个元素的值被设置为了"空字符串",当我们打印数组中的第5个元素的值时,打印出的值就是"空"(注:"空格"不为"空")。

为什么要举这个例子呢?之所以举这个例子,是因为在awk中,元素的值可以设置为"空",在awk中,将元素的值设置为"空字符串"是合法的。

既然在awk中,元素的值可以为"空",那么我们就不能再根据元素的值是否为"空"去判断元素是否存在了,所以,在awk中,如果你使用如下方法判断数组中的元素是否存在,是不合理的,如下图所示。

正如上图所示,第6个元素明明已经存在,但是通过上述方法判断元素是否存在时,仍然显示对应的元素不存在。

其实,使用上述方法判断元素是否存在之所以不合理,除了上述原因,还有另外一个原因,就是当一个元素不存在于数组时,如果我们直接引用这个不存在的元素,awk会自动创建这个元素,并且默认为这个元素赋值为"空字符串",示例如下。

如上图所示,数组中并没有第7个元素,但是当我们输出第7个元素时,输出了"空",所以,出于此原因,在awk中使用之前的方法判断元素是否为空也是不合理的,因为当我们引用一个不存在于数组中的元素时,这个元素其实已经被赋值为"空字符串"了,如下图所示。

那么,在awk中,应该怎样判断元素是否存在呢?我们可以使用如下语法。

如上图所示,我们可以使用语法 "if(下标 in 数组名)" ,从而判断数组中是否存在对应的元素。

当然,我们还可以使用 "!" 对条件进行取反,如下图所示。

在awk中,数组的下标不仅可以为"数字",还可以为"任意字符串",如果你使用过shell中的数组,你可以把awk的数组比作bash中的"关联数组",示例如下

其实,awk中的数组本来就是"关联数组",之所以先用以数字作为下标的数组举例,是为了让读者能够更好的过度,不过,以数字作为数组下标的数组在某些场景中有一定的优势,但是它本质上也是关联数组,awk默认会把"数字"下标转换为"字符串",所以,本质上它还是一个使用字符串作为下标的关联数组。

使用delete可以删除数组中的元素,如下所示

也可以使用delete删除整个数组,如下所示

到目前为止,我们已经介绍了怎样为数组中的元素赋值、怎样输出数组中的某个元素、以及怎样删除数组中的元素,那么现在,我们来聊聊在awk中怎样输出数组中的所有元素,在awk中,如果想要输出数组中的所有元素,则需要借助for循环语句。

你一定看出来了,我们利用了for循环中的变量"i"与数组中的下标都是"数字"的这一特性,按照顺序输出了数组中的元素值。

那么,当数组中的元素的下标为"无规律的字符串"时,我们该怎么办呢?这时可以使用for循环的第二种语法,示例如下。

注意,在这种语法中,for循环中的变量"i"表示的是元素的下标,而并非表示元素的值,所以,如果想要输出元素的值,则需要使用"print 数组名[变量]"

细心如你,一定发现了一个小问题,当数组中的下标为"字符串"时,元素值输出的顺序与元素在数组中的顺序不同,这是因为awk中的数组本质上是关联数组,所以默认打印出的元素是无序的。

那么你可能会提问了,既然之前说过,数字下标最终也会被转换成 "字符串",本质上也是关联数组,既然都属于关联数组,那么为什么第一种for循环语法能够按照顺序输出数组中的元素值呢?

这就是以数字作为下标的优势,因为第一种for循环语法中的变量"i"为数字,由于for循环的原因,"i"是按照顺序递增的,当"i"的值与下标的值相同时,我们即可按照下标的顺序,输出对应元素的值,换句话说就是,我们是通过下标的顺序,输出对应元素值的顺序,也就是键值定位。但是,即使数组元素的下标为数字,如果使用第二种for循环语法,也不能够按照顺序输出,示例如下。

上例又印证我们之前所说的,awk中的数组本质上就是关联数组。

我想,经过上述对比,你应该已经明白了。

前文中,我们都是手动的为数组中的元素赋值,那么我们能不能将指定的文本分割,然后将分割后的字段自动赋值到数组的元素中呢?答案是必须的,但是如果我们想要实现这样的效果,需要借助于split函数,而我们还没有介绍过函数,所以此处就先跳过了,不过需要提前说明的是,通过split函数生成的数组的下标默认是从1开始的,这就是为什么之前说,awk中数组的下标默认是从1开始的了。

在实际的工作中,我们往往会使用数组,统计某些字符出现的次数,比如,我们想要统计日志中每个IP地址出现了多少次,我们就可以利用数组去统计。

但是,统计的时候需要配合一些特殊用法,别着急,我们慢慢聊。

在awk中,我们可以进行数值运算,示例如下

我们将变量a的值设置为1,进行加法计算,每次自加后,再次打印变量a的值,都会加1

这并不难理解,因为上例中,a的值本来就是一个数字。

那么,如果变量a的值是一个字符串,我们能否对变量a进行自加运算呢?我们来试试。

如上图所示,在awk中,当变量a的值为字符串时,竟然也可以进行加法运算,从上例可以看出,awk中,如果字符串参与运算,字符串将被当做数字0进行运算。

那么"空字符串"呢?当空字符串参与运算时,也会被当做数字0吗?我们来试试。

看样子,我们猜的不错,空字符串在参与运算时,也会被当做数字0

之前说过,当我们直接引用一个数组中不存在的元素时,awk会自动创建这个元素,并且为其赋值为"空字符串"。

所以,如果我们引用一个不存在元素,并对其进行自加运算,那么会出现什么效果呢?我们来试一试

如上图所示,当引用了一个不存在的元素时,元素被赋值为空字符串,当对这个元素进行自加运算时,元素的值就变成了1,因为,空字符串在参与运算时,被当做0使用了,所以,综上所述,我们对一个不存在的元素进行自加运算后,这个元素的值就变成了自加运算的次数,自加x次,元素的值就被赋值为x,自加y次,元素的值就被赋值为y,示例如下。

利用这一点,我们就可以统计文本中某些字符出现的次数,比如IP地址,示例如下。

当然,看懂上图中的命令,需要掌握前文中的知识,同时需要理解今天所介绍的知识。

上图中,我们使用了一个空模式,一个END模式。

空模式中,我们随便创建了一个数组,并且将IP地址作为引用元素的下标,进行了引用,所以,当执行到第一行时,我们引用的是count["192.168.1.1"]

很明显,这个元素并不存在,所以,当第一行被空模式中的动作处理完毕后,count["192.168.1.1"]的值已经被赋值为1了。

由于END模式中的动作会最后执行,所以我们先不考虑END模式。

这时,空模式中的动作继续处理下一行,而下一行的IP地址为192.168.1.2

所以,count["192.168.1.2"]第一次参与运算的过程与上述过程同理。

其他IP地址第一次参与运算的过程与上述过程同理。

直到再次遇到相同的IP地址时,使用同样一个IP地址作为下标的元素将会再次被自加,每次遇到相同的IP地址,对应元素的值都会加1。

直到处理完所有行,开始执行END模式中的动作。

而END模式中,我们打印出了count数组中的所有元素的下标,以及元素对应的值。

此刻,count数组中的下标即为IP地址,元素的值即为对应IP地址出现的次数。

最终,我们统计出了每个IP地址出现的次数。

其实,我们就是利用了之前所演示的一个知识点:

我们对一个不存在的元素进行自加运算后,这个元素的值就变成了自加运算的次数

上述过程可能比较绕,如果你之前没有接触过awk,一遍看不懂是很正常的,自己按照上述过程动手做几遍,细细品味一番,相信你会搞明白的。

如果你以后再想统计文本中某类文本出现的"次数",就可以使用上述套路了,活学活用以后,你会发现上述套路特别好使。

比如,如果我们想要统计如下文本中每个人名出现的次数,我们则可以使用如下命令。

关于awk中数组的用法,就先总结到这里,这些知识已经能够满足我的日常使用了,但是这些并不是数组的全部,如果你想要更加深入的了解数组,可以参考官方手册的数组部分,链接如下。

最常用的算数函数有rand函数、srand函数、int函数。

可以使用rand函数生成随机数,但是使用rand函数时,需要配合srand函数,否则rand函数返回的值将一直不变,示例如下。

可以看到,如果单纯的使用rand函数,生成的值是不变的,可以配合srand函数,生成一个大于0小于1的随机数,示例如下

可以看到,上图中生成的随机数都是小于1的小数,如果我们想要生成整数随机数,可以将上述生成的随机数乘以100,然后截取整数部分,使用int函数可以截取整数部分的值,示例如下

经过上述处理以后,可以得到一个小于100的随机整数。

我们可以使用gsub函数或sub函数替换某些文本,先来 看看gsub函数怎样使用。

如果我们想要将如下文本中的第一列中的小写字母"l"都替换成大写字母"L",则可以使用gsub函数,示例如下。

如上图所示,我们使用gsub函数,将小写字母"l"替换成大写字母"L",但是替换的范围只限于"1"换成"0",示例如下。

看完上述示例,我想你应该已经明白了gsub函数的作用,没错,gsub函数会在指定范围内查找指定的字符,并将其替换为指定的字符串。

其实,我们还可以根据正则表达式,替换字符串,示例如下。

好了,经过上述示例,你应该已经明白gsub的用法了。

那么sub函数与gsub函数有什么不同呢?我们来对比一下。

细心如你一定已经发现了,当使用gsub函数时,gsub会替换指定范围内的所有符合条件的字符。

而使用sub函数则不同,当使用sub函数时,sub函数只会替换指定范围内第一次匹配到的符合条件的字符。

我们可以把gsub函数的作用理解为指定范围内的全局替换。

可以把sub函数的作用理解为指定范围内的单次替换,只替换第一次匹配到的字符。

这就是sub函数与gsub函数的为唯一的不同之处。

我们可以通过length函数,获取到指定字符串的长度,示例如下

如上图所示,我们输出了文本中每个单词的长度,其实,length函数可以省略传入的参数,即不指定任何字符换,当省略参数时,默认使用"$0"作为参数,示例如下。

正如上图所示,我们使用length函数,获取到了文本中每一行的长度。

我们可以使用index函数,获取到指定字符位于整个字符串中的位置,示例如下

上图中,我们使用index函数,在每一行中咋找字符串"Lee",如果Lee存在于当前行,则返回字符串Lee位于当前行的位置,如果Lee不存在于当前行,则返回0,表示当前行并不存在Lee,如上图所示,第二行中包含Lee,而且Lee位于第二行的第7个字符的位置,所以返回数字7。

在前文中,我们在总结数组时,提到过一个函数,借助这个函数可以动态的生成数组,而不用手动的设置数组中每个元素的值,没错,这个函数就是split函数。通过split函数,我们可以将指定的字符串按照指定的分割符切割,将切割后的每一段赋值到数组的元素中,从而动态的创建数组,示例如下。

如上图所示,我们通过split函数,将字符串ts切割了,以":"作为分割符,将分割后的字符串保存到了名为huluwa的数组中,当我们输出数组中的元素时,每个元素的值为分割后的字符,其实,split函数也有对应的返回值,其返回值就是分割以后的数组长度,示例如下。

注意,被split函数分割后的数组的元素下标从1开始,不像其他语言中的数组下标是从0开始的,而且数组中元素输出的顺序可能与字符串中字符的顺序不同,原因在前文中已经说过了,如果我们想要按照顺序输出数组中的元素,可以使用如下方法。

我们先使用了split函数生成了数组,并且将split的返回值保存在变量arrlen中,然后利用for循环中变量的递增,顺序的输出了数组中的对应下标以及元素值,如果你不明白为什么,请参考前文。

我们还能够通过asort函数根据元素的值进行排序,但是,经过asort函数排序过后的数组的下标将会被重置,示例如下

如上图所示,数组中元素的值均为数字,但是下标为自定义的字符串,通过asort函数对数组排序后,再次输出数组中的元素时,已经按照元素的值的大小进行了排序,但是,数组的下标也被重置为了纯数字,其实,asort还有一种用法,就是在对原数组元素值排序的同时,创建一个新的数组,将排序后的元素放置在新数组中,这样能够保持原数组不做任何改变,我们只要打印新数组中的元素值,即可输出排序后的元素值,示例如下。

其实,asort函数也有返回值,它的返回值就是数组的长度,换句话说,asort的返回值就是数组中元素的数量,示例如下。

使用asort 函数可以根据元素的值进行排序,而使用asorti 函数可以根据元素的下标进行排序。

当元素的下标为字符串时,我们可以使用asorti 函数,根据下标的字母顺序进行排序,当元素的下标为数字时,我们就没有必要使用函数排序了,直接使用for循环即可排序,所以,此刻我们只考虑数组的下标为字符串时,怎样通过asorti 函数根据下标对数组进行排序。

当数组的下标为字符串时,asorti 函数会根据原数组中的下标的字母顺序进行排序,并且将排序后的下标放置到一个新的数组中,并且asorti函数会返回新的数组的长度,示例如下

如上图所示,asorti 函数根据数组t的下标排序后,创建了一个新的数组newt,newt中元素的值即为t数组下标的值,上例中,我们使用len变量保存了asorti函数的返回值,并且输出了最后排序后的新数组。

那么,聪明如你,一定想到了,既然我们已经将t数组的下标排序输出了,那么我们一定可以根据排序后的下标再次输出对应的元素值,从而达到根据数组下标排序后,输出原数组元素的目的,示例如下。

没错,上述过程,其实就是新数组负责排序老数组的下标,并将排序后的下标作为新数组的元素,而我们输出新数组元素的同时,又将新数组的元素值作为老数组下标,从而输出了老数组中的元素值,这句话好绕,不过我觉得你应该明白了。

十一、拾遗之”三元运算”与”打印奇偶行”

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

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

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

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

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

条件 ? 结果1 : 结果2

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

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

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

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

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

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

是不是很容易理解?你一定已经明白了。

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

我们先来看看怎样使用awk打印奇数行或偶数行,然后再结合示例解释原理,所以看不懂没关系,后面会有解释。

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

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

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

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

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

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

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

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

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

那么,如果我们将上例中awk命令中的动作都省略,会出现什么情况呢?我们来试试。

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

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

这就是第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"与空字符串表示假,当模式为假时,不会执行对应的动作,而当存在模式并省略动作时,默认动作为打印整行,但是由于模式为假,所以对应的动作并未执行。

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

正如上图所示,我们使用"!"对真假进行取反。

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

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

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

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

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

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

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

}

华为采用机器翻译与人工审校相结合的方式将此文档翻译成不同语言,希望能帮助您更容易理解此文档的内容。 请注意:即使是最好的机器翻译,其准确度也不及专业翻译人员的水平。 华为对于翻译的准确性不承担任何责任,并建议您参考英文文档(已提供链接)。

}

以下文字资料是由(历史新知网)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

}

我要回帖

更多关于 (x-3)÷2=7.5解方程 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信