linux--AWK使用方法

linux–AWK使用方法

一,awk基础

 

awk是一个报告生成器,它拥有强大的文本格式化的能力,这就是专业的说法。

你可能不理解所谓的报告生成器中的"报告"是什么,你可以把"报告"理解为"报表"或者"表格",也就是说,我们可以利用awk命令,将一些文本整理成我们想要的样子,比如把一些文本整理成"表"的样子,然后再展示出来,刚才概念中提到的"文本格式化的能力",也就是这个意思,其实这样说可能还是不太容易理解,不用着急,当你看到后面的"示例"时,自然会明白awk所擅长的"文本格式化"能力是什么。

 

awk是由Alfred Aho 、Peter Weinberger 和 Brian Kernighan这三个人创造的,awk由这个三个人的姓氏的首个字母组成。

awk早期是在unix上实现的,所以,我们现在在linux的所使用的awk其实是gawk,也就是GNU awk,简称为gawk,awk还有一个版本,New awk,简称为nawk,但是linux中最常用的还是gawk。

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

awk其实是一门编程语言,它支持条件判断、数组、循环等功能。所以,我们也可以把awk理解成一个脚本语言解释器。

 

grep 、sed、awk被称为linux中的"三剑客"。

我们总结一下这三个"剑客"的特长。

grep 更适合单纯的查找或匹配文本

sed  更适合编辑匹配到的文本

awk  更适合格式化文本,对文本进行较复杂格式处理

 

此处,我们只总结 awk

 
 

awk基础

awk基本语法如下,看不懂没关系,我们会慢慢举例。

awk [options] 'program' file1 , file2 , ```

对于上述语法中的program来说,又可以细分成pattern和action,也就是说,awk的基本语法如下

awk [options] 'Pattern{Action}' file

 

从字面上理解 ,action指的就是动作,awk擅长文本格式化,并且将格式化以后的文本输出,所以awk最常用的动作就是print和printf,因为awk要把格式化完成后的文本输出啊,所以,这两个动作最常用。

 

我们先从最简单用法开始了解awk,我们先不使用[options] ,也不指定pattern,直接使用最简单的action,从而开始认识awk,示例如下

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

上图中,我们只是使用awk执行了一个打印的动作,将testd文件中的内容打印了出来。

 

好了,现在,我们来操作一下另一个类似的场景。

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

上图中的示例没有使用到options和pattern,上图中的awk '{print $5}',表示输出df的信息的第5列,$5表示将当前行按照分隔符分割后的第5列,不指定分隔符时,默认使用空格作为分隔符,细心的你一定发现了,上述信息用的空格不止有一个,而是有连续多个空格,awk自动将连续的空格理解为一个分割符了,是不是比cut命令要简单很多,这样比较简单的例子,有利于我们开始了解awk。

 

awk是逐行处理的,逐行处理的意思就是说,当awk处理一个文本时,会一行一行进行处理,处理完当前行,再处理下一行,awk默认以"换行符"为标记,识别每一行,也就是说,awk跟我们人类一样,每次遇到"回车换行",就认为是当前行的结束,新的一行的开始,awk会按照用户指定的分割符去分割当前行,如果没有指定分割符,默认使用空格作为分隔符。

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

 
 

$0 表示显示整行 ,$NF表示当前行分割后的最后一列($0和$NF均为内置变量)

注意,$NF 和 NF 要表达的意思是不一样的,对于awk来说,$NF表示最后一个字段,NF表示当前行被分隔符切开以后,一共有几个字段。

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

 
 

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

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

 
 

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

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

 

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

我们说过,awk的语法如下

awk [options] 'Pattern{Action}' file

而且我们说过awk是逐行处理的, 刚才已经说过了最常用的Action:print

现在,我们来认识下一Pattern,也就是我们所说的模式

不过,我们准备先把awk中最特殊的模式展示给大家,以后再介绍普通的模式,因为普通模式需要的篇幅比较长,所以我们先来总结特殊模式。

AWK 包含两种特殊的模式:BEGIN 和 END。

BEGIN 模式指定了处理文本之前需要执行的操作:

END 模式指定了处理完所有行之后所需要执行的操作:

什么意思呢?光说不练不容易理解,我们来看一些小例子,先从BEGIN模式开始,示例如下

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

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

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

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

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

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

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

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

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

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

awk从放弃到入门(1):awk基础 (通俗易懂,快进来看)

</p>

上述示例中返回的结果有没有很像一张"报表",有"表头"  、"表内容"、  "表尾",awk对文本的格式化能力你体会到了吗?

 

		</div>

二、AWK分隔符

awk有哪些分隔符,awk的默认分割符是空格,但是,这样描述并不精确,因为,awk的分隔符还分为两种,"输入分隔符" 和 "输出分隔符" 。

此处我们先将分隔符的概念列出,看不懂没关系,我们会一一进行举例,到时自然会明白。

 

输入分隔符,英文原文为field separator,此处简称为FS

输入分割符,默认是空白字符(即空格),awk默认以空白字符为分隔符对每一行进行分割。

 

输出分割符,英文原文为output field separator,此处简称为OFS

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

 

光看概念是不是不容易搞明白?没关系,我们来动手实践一下,胜过千言万语。

 

输入分隔符

我们现在通过一些小例子,搞明白这两个分隔符的作用,不过我们要一个一个来,我们先看一些"输入分隔符"的小例子

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

awk从放弃到入门(2):awk分隔符

</p>

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

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

awk从放弃到入门(2):awk分隔符

</p>

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

 

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

我们说过,awk的语法如下

awk [options] 'Pattern{Action}' file

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

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

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

 

输出分隔符

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

awk从放弃到入门(2):awk分隔符

</p>

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

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

awk从放弃到入门(2):awk分隔符

</p>

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

awk从放弃到入门(2):awk分隔符

</p>

 

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

awk从放弃到入门(2):awk分隔符

</p>

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

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

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

 

好了,awk的分隔符就总结到这里,希望这篇文章能对你有所帮助。

 

		</div>

三、 AWK变量

 

之前的文章在使用到"输入分隔符"和"输出分隔符"的时候,我们都提到了一个名词:"变量"。

这篇文章我们就来详细的总结一下awk中的变量,我们会先对概念进行描述,如果概念中有不明白的地方,不要着急,对应其示例,你自然就会明白。

 

对于awk来说"变量"又分为"内置变量" 和 "自定义变量" , "输入分隔符FS"和"输出分隔符OFS"都属于内置变量。

内置变量就是awk预定义好的、内置在awk内部的变量,而自定义变量就是用户定义的变量。

 

我们先看看awk常用的一些内置变量,此处先大致列出其概念,只看概念并不容易理解其意思,不懂没关系,等到示例时你自然会明白。

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

 

FS:输入字段分隔符, 默认为空白字符

OFS:输出字段分隔符, 默认为空白字符

RS:输入记录分隔符(输入换行符), 指定输入时的换行符

ORS:输出记录分隔符(输出换行符),输出时用指定符号代替换行符

NF:number of Field,当前行的字段的个数(即当前行被分割成了几列),字段数量

NR:行号,当前处理的文本行的行号。

FNR:各文件分别计数的行号

FILENAME:当前文件名

ARGC:命令行参数的个数

ARGV:数组,保存的是命令行所给定的各参数

 

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

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

 

内置变量

内置变量NR

NR比较简单,我们先看NR的例子。

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

awk从放弃到入门(3):awk变量

</p>

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

awk从放弃到入门(3):awk变量

</p>

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

awk从放弃到入门(3):awk变量

</p>

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

 

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

 

内置变量FNR

FNR这个内置变量是什么意思呢?我们一起来看看。

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

awk从放弃到入门(3):awk变量

</p>

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

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

awk从放弃到入门(3):awk变量

</p>

我想,对比完上述两个示例,你肯定明白了FNR内置变量的作用,没错,它的作用就是当awk处理多个文件时,分别对每个文件的行数进行计数。

 

内置变量RS

现在,我们来看看RS这个变量,我们说了,RS是输入行分隔符,如果不指定,默认的"行分隔符"就是我们所理解的"回车换行"。

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

awk从放弃到入门(3):awk变量

</p>

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

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

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

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

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

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

 

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

 

内置变量ORS

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

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

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

awk从放弃到入门(3):awk变量

</p>

 

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

 

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

 

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

awk从放弃到入门(3):awk变量

</p>

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

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

 

内置变量FILENAME

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

awk从放弃到入门(3):awk变量

</p>

 

内置变量ARGC与ARGV

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

别眼花了。

一个是ARGC,

一个是ARGV,

先说说ARGV。

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

awk从放弃到入门(3):awk变量

</p>

上图中,我们先使用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从放弃到入门(3):awk变量

</p>

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

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

 

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

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

awk从放弃到入门(3):awk变量

</p>

 

自定义变量

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

方法一:-v varname=value  变量名区分字符大小写。

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

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

 

通过方法一自定义变量。

awk从放弃到入门(3):awk变量

</p>

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

 

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

awk从放弃到入门(3):awk变量

</p>

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

awk从放弃到入门(3):awk变量

</p>

 

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

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

awk从放弃到入门(3):awk变量

</p>

 

好了,awk中变量的使用方法就暂时总结到这里,希望这篇文章会对你有所帮助。

 

		</div>

四、 AWK格式化

现在,我们来更加深刻的了解一下awk的格式化能力,在前文的举例中,我们在使用awk时,通常使用print 对文本进行输出,但是动作print 只能实现简单的文本输出功能,并不能对文本格式进行改变,如果想要改变文本的格式,则需要awk中的另一个动作,此动作与print很像,它就是printf,没错,看到printf,你肯定会想到printf命令,或者想到了C语言中的printf()函数,如果你想到了这些,那么使用printf对你来说应该不在话下了,如果你并没有接触过printf命令或者printf()函数,没有关系,你可以先阅读如下连接对应的文章,即可学会printf命令的使用方法

printf命令详解

 

在阅读本文后面的内容之前,请先确保你已经阅读了如下文章:

printf命令详解

 

在阅读本文后面的内容之前,请先确保你已经掌握了awk的相关基础知识,如果你还没有掌握它,建议按照顺序参考如下连接中的文章:

AWK命令总结之从放弃到入门

 

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

awk从放弃到入门(4):awk格式化

</p>

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

 

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

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

awk从放弃到入门(4):awk格式化

</p>

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

awk从放弃到入门(4):awk格式化

</p>

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

awk从放弃到入门(4):awk格式化

</p>

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

awk从放弃到入门(4):awk格式化

</p>

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

 

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

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

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

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

 

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

 

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

awk从放弃到入门(4):awk格式化

</p>

 

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

awk从放弃到入门(4):awk格式化

</p>

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

 

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

awk从放弃到入门(4):awk格式化

</p>

 

其实话说回来,只要能够灵活的使用printf命令,再结合printf动作使用时的3个注意点,即可快速灵活的掌控它,好了,关于awk的格式化能力,就暂时总结到这里,希望这篇文章能够对你有所帮助。

 

		</div>

五、AWK模式之pattern(1)

我们之前一直在提,awk的使用语法如下(我想你已经很熟悉了):

awk  [options]  'Pattern {Action}'  file1  file2 ···

 

对于options(选项)而言,我们使用过-F选项,也使用过-v选项。

对于Action(动作)而言,我们使用过print与printf,之后的文章中,我们还会对Action进行总结。

对于Pattern(模式)而言,我们在刚开始学习awk时,就介绍了两种特殊模式,BEGIN模式和END模式,但是,我们并没有详细的介绍"模式"是什么,怎么用,而此处,我们将详细的介绍一下awk中的模式。

 

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

 

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

 

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

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

awk从放弃到入门(5):awk模式(Pattern)之一

</p>

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

 

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

awk从放弃到入门(5):awk模式(Pattern)之一

</p>

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

 

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

关系运算符

含义

用法示例

<

小于

x < y

<=

小于等于

x <= y

==

等于

x == y

!=

不等于

x != y

>=

大于等于

x >= y

>

大于

x > y

~

与对应的正则匹配则为真

x ~ /正则/

!~

与对应的正则不匹配则为真

x !~ /正则/

 

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

 
 

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

awk从放弃到入门(5):awk模式(Pattern)之一

</p>

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

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

 

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

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

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

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

 

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

awk从放弃到入门(5):awk模式(Pattern)之一

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

 

其实,"模式"还有其他的"样子",只是我们今天还没有见识到而已,不过,今天的时光也没有白费,起码我们知道了"模式"的概念,以及如下三类"模式"

1、空模式

2、关系运算模式

3、BEGIN/END模式

 

		</div>

六、 AWK模式之pattern(2)

 

在前文中,我们已经认识了awk的模式,而且,我们已经介绍了awk中的3中模式

1、空模式

2、关系运算模式

3、BEGIN/END模式

 

那么今天,我们就来介绍一下awk的另外两种常用模式,正则模式与行范围模式,别着急,我们一个一个慢慢聊。

 

正则模式

先说说什么是正则模式。

见名知义,"正则模式"肯定与"正则表达式"有关,所以,如果想要使用这种模式,则必须先学会在Linux中使用正则表达式,如果你对正则表达式还不是特别熟悉,可以参考博客中的系列文章:在Linux中使用正则表达式

 

前文中提到过,"模式"可以理解为"条件",当不指定模式时,文本中的每一行都会执行对应的动作,当指定模式时,只有被模式匹配到的、符合条件的行才会执行对应的动作。

那么什么是正则模式呢?正则模式可以理解为,把"正则表达式"当做"条件",能与正则匹配的行,就算满足条件,满足条件的行才会执行对应的动作,不能被正则匹配到的行,则不会执行对应的动作。如果你觉得我说的不明白,来看一个小例子,就能理解。

 

不过在进行示例之前,我们先来思考一个小问题。

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

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

1、从/etc/passwd文件中找出符合条件的行(用户名以zsy开头的用户)。

2、找出符合条件的文本行以后,以":"作为分隔符,将文本行分段。

3、取出我们需要的字段,格式化输出。

4、结合BEGIN模式,输出一个格式化以后的文本,提高可读性。

 

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

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

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

 

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

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

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

 

示例如下

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

 

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

 

行范围模式

现在聊聊行范围模式。

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

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

 

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

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

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

你是不是和我一样,学了新知识,忘了旧知识呢?就像上述示例一样,你是不是总在尝试使用"正则模式"解决问题,而忘记了使用"关系表达式模式"解决问题呢?

 
 

其他

其实,在学习"关系表达式模式"时,有一个"关系运算符"需要与"正则模式"配合使用,它就是"~"

还记得我们之前总结的一些常用的关系运算符吗,我们来回顾一下。

关系运算符

含义

用法示例

<

小于

x < y

<=

小于等于

x <= y

==

等于

x == y

!=

不等于

x != y

>=

大于等于

x >= y

>

大于

x > y

~

与对应的正则匹配则为真

x ~ /正则/

!~

与对应的正则不匹配则为真

x !~ /正则/

没错,细心如你一定发现了,关系运算符"~"与关系运算符"!~"都需要配合"正则模式"使用。

我们来看一个小示例,就更容易理解了。

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

 

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

awk从放弃到入门(6):awk模式(Pattern)之二

</p>

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

 

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

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

1、空模式

2、关系运算模式

3、正则模式

4、行范围模式

5、BEGIN/END模式

  

		</div>

七、 AWK动作总结

 

这篇文章中的知识点是建立在前文的基础上的,如果你还没有掌握前文中的知识,请先参考之前的文章。

注:在阅读这篇文章之前,最好已经了解了一些基本的开发语法,比如,if、if...else、for、while等,否则在阅读时 有可能遇到障碍。

 

在前文中,我们已经使用过了awk的选项、模式 以及 动作。

这篇文章中,我们再来聊聊动作。

 

不知从何说起,我们还是从之前的示例开始聊吧,回顾一个小例子,如下图所示。

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

如上图所示,红线标注部分就是awk命令中的"动作",我想你应该已经非常熟悉了。

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

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

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

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

蓝线标注为第二部分:"print $0"

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

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

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

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

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

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

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

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

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

必须能啊,示例如下。

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

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

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

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

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

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

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

 

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

if(条件)

{
语句1;
语句2;

}

 

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

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

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

"if(NR == 1)"中的NR为awk的内置变量,NR为行号之意,所以,"if(NR == 1)"表示行号为1时,条件成立。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

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

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

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

 

编程语言中,除了"if"之外,还有"if...else..."或者"if...else if...else"这样的语法,awk中也有这样的用法。

"if...else..."的语法如下:

if(条件)

{
语句1;
语句2;

}
else
{
语句1;
语句2;

}

"if...else if...else"的语法如下:

if(条件1)

{
语句1;
语句2;

}
else if(条件2)
{
语句1;
语句2;

}
else
{
语句1;
语句2;

}

 

其实,这些语法与编程语言中的用法都是相同的,我相信你已经明白了,我们直接来看一些小示例吧。

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

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

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

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

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

 

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

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

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

 

		</div>				

 

 

前文中,我们介绍了awk中的条件判断语句。

比如 "if"

比如 "if...else..."

比如 "if...else if...else..."

我们说过,这些语句都可以归类为"控制语句",但是这些并不是"控制语句"的全部,今天我们继续聊聊awk的"控制语句"。

 

今天的话题主要与"循环"有关,说到"循环",你一定会想起 for、while 等字眼,没错,awk中也有for循环、while循环等控制语句。

其实,只要我们有一点点开发基础,理解这些都不是什么难事,因为它们的用法与其他编程语言中的用法并没与什么区别。

 

我们先来看一下这些循环控制语句的语法,然后再进行举例。

 

#<#for循环语法格式1

for(初始化; 布尔表达式; 更新) {
//代码语句
}
 
#for循环语法格式2
for(变量 in 数组) {
//代码语句
}
 
#while循环语法
while( 布尔表达式 ) {
//代码语句
}
 
#do…while循环语法
do {
//代码语句
}while(条件)

   

好了,了解了语法,现在来看看示例,就从for循环开始吧。

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

awk从放弃到入门(8):awk动作总结之二

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

 

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

awk从放弃到入门(8):awk动作总结之二

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

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

awk从放弃到入门(8):awk动作总结之二

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

 

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

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

continue的作用:跳出"当前"循环

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

 

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

awk从放弃到入门(8):awk动作总结之二

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

 

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

awk从放弃到入门(8):awk动作总结之二

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

 

continue与break同样可以用于while循环与do...while循环,此处就不再赘述了。

 

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

awk从放弃到入门(8):awk动作总结之二

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

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

awk从放弃到入门(8):awk动作总结之二

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

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

 

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

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

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

awk从放弃到入门(8):awk动作总结之二

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

 

awk的常用的流程控制语句与循环语句都已经总结完毕了,希望这篇文章能够帮助到你。

 

		</div>

八、 AWK数组详解

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

 

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

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

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

awk从放弃到入门(9):awk数组详解

</p>

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

awk从放弃到入门(9):awk数组详解

</p>

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

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

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

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

awk从放弃到入门(9):awk数组详解

</p>

 

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

awk从放弃到入门(9):awk数组详解

</p>

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

 

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

awk从放弃到入门(9):awk数组详解

</p>

 

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

 

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

awk从放弃到入门(9):awk数组详解

</p>

 

到目前为止,我们已经介绍了怎样为数组中的元素赋值、怎样输出数组中的某个元素、以及怎样删除数组中的元素,那么现在,我们来聊聊在awk中怎样输出数组中的所有元素,在awk中,如果想要输出数组中的所有元素,则需要借助for循环语句,还记得在前文中介绍for循环时,有两种for循环语法吗?我们来回顾一下。

#for循环语法格式1

for(初始化; 布尔表达式; 更新) {
//代码语句
}
 
#for循环语法格式2
for(变量 in 数组) {
//代码语句
}

这两种for循环语法都能够遍历输出数组中的元素,不过第一种for循环语法只能输出以数字作为下标的数组,示例如下

awk从放弃到入门(9):awk数组详解

</p>

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

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

 

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

 

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

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

awk从放弃到入门(9):awk数组详解

</p>

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

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

 

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

 

实例应用

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

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

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

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

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

awk从放弃到入门(9):awk数组详解

</p>

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

 

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

awk从放弃到入门(9):awk数组详解

</p>

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

 

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

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

awk从放弃到入门(9):awk数组详解

</p>

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

awk从放弃到入门(9):awk数组详解

</p>

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

awk从放弃到入门(9):awk数组详解

</p>

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

上图中,我们使用了一个空模式,一个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从放弃到入门(9):awk数组详解

</p>

 

		</div>

九、 AWK 内置函数

在awk中,可以自定义函数,也有内置的函数,今天我们就来总结一些常用的内置函数。

 

awk的内置函数大致可以分类为算数函数、字符串函数、时间函数、其他函数等,此处只总结一下个人觉得常用的函数。

 

算数函数

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

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

awk从放弃到入门(10):awk内置函数

</p>

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

awk从放弃到入门(10):awk内置函数

</p>

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

awk从放弃到入门(10):awk内置函数

</p>

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

  

字符串函数

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

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

awk从放弃到入门(10):awk内置函数

</p>

如上图所示,我们使用gsub函数,将小写字母"l"替换成大写字母"L",但是替换的范围只限于"$1",所以,当我们再次输出文本时,发现只有文本中的第一列中的小写字母"l"被替换成了大写字母"L",其他列中的小写字母"l"并未被替换,当然,如果你想要替换文本中所有的小写字母"l",则可以将上图中的"$1"换成"$0",或者省略gsub函数中的第三个参数,省略gsub中的第三个参数时,默认为"$0",示例如下。

awk从放弃到入门(10):awk内置函数

</p>

 

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

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

awk从放弃到入门(10):awk内置函数

</p>

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

 

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

awk从放弃到入门(10):awk内置函数

</p>

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

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

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

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

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

 

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

awk从放弃到入门(10):awk内置函数

</p>

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

awk从放弃到入门(10):awk内置函数

</p>

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

 

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

awk从放弃到入门(10):awk内置函数

</p>

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

 

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

awk从放弃到入门(10):awk内置函数

</p>

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

awk从放弃到入门(10):awk内置函数

</p>

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

awk从放弃到入门(10):awk内置函数

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

   

其他函数

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

awk从放弃到入门(10):awk内置函数

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

awk从放弃到入门(10):awk内置函数

 

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

awk从放弃到入门(10):awk内置函数

 

理解完asort 函数,我们来认识一下asorti 函数,仔细看,是 asort 与 asorti

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

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

 

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

awk从放弃到入门(10):awk内置函数

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

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

awk从放弃到入门(10):awk内置函数

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

  

		</div>
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值