内容简介:那是难堪的一幕,记得那是一次面试,面试官问我:如何查看Linux系统上的所有用户?我答曰:看看/home路径下有哪些文件夹,每个用户都有一个以自己用户名为名字的主目录。
从一道面试题说起
那是难堪的一幕,记得那是一次面试,面试官问我:
如何查看 Linux 系统上的所有用户?
我答曰:看看/home路径下有哪些文件夹,每个用户都有一个以自己用户名为名字的主目录。
面试官反问到:如果我在/home下随便建立一个目录,那就是说又会增加一个新的用户喽?用户的主目录只能在/home目录下喽?
我不语,只是感觉到好尴尬。好吧,结果还是不错的,面试成功了。回来以后,Google了一下这个问题的答案。
是时候写篇总结来告诉大家,我也在玩awk。
我也玩玩awk
如果还不知道awk,去Google一下吧。可能会吓到你,可以说是上古的东西了,应该比你的年龄都大了吧。就是这么神奇,就这么个“老古董”,在新技术层出不穷的今天,依然保持着它的那份活力和自信——存在即合理。
awk的优点和使用场合非常明确,它非常适用于处理结构化数据和生成表单,与sed和grep有些类似,但是功能远强大于二者,由于awk具备脚本语言的特点,也算是一门脚本语言,这篇文章将按照下图所示的展开总结。
还等什么,撸起袖子,开干吧。
awk初体验
话又说回来,接着文章开头的那道面试,下面使用awk来完成这道题目。
awk 'BEGIN{ FS=":" printf("%-10s%-20s\n", "UserName", "HomeDir") print "==============================" } { printf("%-10s%-20s\n", $1, $6) } END{ print "==============================" printf("User(s):%d\n", NR) print "==============================" }' /etc/passwd
对于awk脚本的运行,有两种方式:
- awk [POSIX or GNU style options] -f progfile [--] file
- awk [POSIX or GNU style options] [--] 'program' file
我上面的脚本使用的是第二种方式,我们也可以将脚本单独写入一个文件:
BEGIN{ FS=":" printf("%-10s%-20s\n", "UserName", "HomeDir") print "==============================" } { printf("%-10s%-20s\n", $1, $6) } END{ print "==============================" printf("User(s):%d\n", NR) print "==============================" }
将上述脚本写入listUser.awk这个文件,然后在命令行执行:
awk -f listUser.awk /etc/passwd
对于上面这段awk脚本看不懂不要紧,看完下面的内容,你再回过头来看这段脚本,就会豁然开朗。
透过现象看本质
我还是通过上面的awk代码来说说awk的原理和本质,只有掌握了原理,以后和awk打交道时才会游刃有余。还是先上一副图。
从上图中可以看出,awk在工作时主要分为以下三个部分:
- BEGIN,也就是所谓的初始化模块,比如定义分隔符,初始化一些值等,它会在数据处理部分执行之前执行,并且只执行一次,这一部分是可选的;
- 数据处理,这一部分也就是awk脚本的核心部分,它是一对以模式
pattern
与大括号括起来的操作action
组合而成的,二者可能会出现以下组合:
- pattern {action} 记录匹配对应的模式,则执行大括号中的操作
- pattern 记录匹配对应的模式,则直接打印记录
- {action} 对每一条记录都执行大括号中的操作
数据处理模块会循环读取待处理文件中的记录,每次读取一条记录,处理完一条以后再读取下一条记录,直至所有记录被读取完毕。
-
END,是最终的收尾处理模块,它会在所有数据处理完成以后才执行,并且只执行一次;比如我们处理完数据了,需要输出一共处理了多少条记录,多少个字段等信息,就可以在END部分进行输出,这一部分也是可选的。
如果你想说这对awk工作原理的总结不够透彻,但是掌握这些,你就已经强于80%的人了,不是么?如果你还想知道的更多,去Google吧,因为再深了,我也不会,文章标题就已经表明了,我是以“玩”的态度来学习awk的,不必过分的在于细节,不求甚解。
入门第一步——记录与字段
记录和字段是awk中非常重要的两个概念,在我们处理文件时,总是会说到读入一条记录,然后根据分隔符,分隔成字段,我们先来把awk中的这两个概念搞清楚喽。
上面也说了,awk特别适用于处理结构型的数据;在决定是否选择awk来处理数据时,请检查数据是否具有结构性,而不只是一串无规则的字符。就如上图所示, jelly:b3SooaHxlCicE:202:20::/home/jelly:/sbin/sh
这就是一条记录,我们可以将 :
作为分隔符,分隔这条记录,就可以得到图中所示各个字段,这就是对awk中记录与字段最直白的解释。
同时,为了更方便操作字段,在awk中可以使用字段操作符 $
来指定字段,在 $
后面跟一个数字或变量就表示对应字段的内容,比如:
$0 #输出整个记录的值:jelly:b3SooaHxlCicE:202:20::/home/jelly:/sbin/sh $1 #输出字段1的值:jelly $6 #输出字段6的值:/home/jelly $(1+2) #输出字段3的值:202
入门第二步——表达式
在awk中,我们可以向其它高级语言那样,很轻松的就可以使用表达式来进行计算、检索等操作。表达式由数字和字符串常量、变量、操作符、函数和正则表达式组成。
由于awk是脚本语言,没有高级语言中那些复杂的数据类型定义。在awk中定义变量时,每个变量都有一个字符串类型值和数字类型值,awk将根据表达式的前后关系选择合适的值,变量也可以不用初始化,awk自动将它们初始化为空字符串,如果当做数字使用时,其值则为0。例如以下代码:
a = 1 #将一个数字赋值给变量 b = "Hello World" #将一个字符串赋值给变量
定义变量以后,就需要说说各种运算操作了。先来看看算术操作符,在awk常用的算术操作符有以下这些:
操作符 | 描述 | 举例 |
---|---|---|
+ | 加 | awk '{x=2; y=3; print x+y}' |
- | 减 | awk '{x=2; y=3; print x-y}' |
* | 乘 | awk '{x=2; y=3; print x*y}' |
/ | 除 | awk '{x=2; y=3; print x/y}' |
% | 取模运算 | awk '{x=7; y=3; print x%y}' |
^ | 幂运算 | awk '{x=2; y=3; print x^y}' |
再来看看和其它高级语言一样拥有的高级赋值操作符:
操作符 | 描述 | 举例 |
---|---|---|
++ | 变量加1,分为前置加和后置加 | awk '{print x++}' |
-- | 变量减1,分为前置减和后置减 | awk '{print --x}' |
+= | 将加的结果赋值给变量 | awk '{print x+=1}' |
-= | 将减的结果赋值给变量 | awk '{print x-=1}' |
*= | 将乘的结果赋值给变量 | awk '{x=2; print x*=2}' |
/= | 将除的结果赋值给变量 | awk '{x=4; print x/=2}' |
%= | 将取模的结果赋值给变量 | awk '{x=5; print x%=2}' |
^= | 将幂运算的结果赋值给变量 | awk '{x=2; print x^=3}' |
很多时候,我们需要将两个字符串连接在一起,怎么搞呢?看看吧。
awk '{x="Hello" "World"; print x}'
在awk中就是通过 空格 来连接字符串,就是如此简单。
入门第三步——系统变量
在awk中有很多的系统变量,这些系统变量在我们编写awk脚本的时候会经常使用到,我现在将经常使用到的系统变量列举出来,并做简要说明。
变量名 | 描述 | 举例 |
---|---|---|
$0 | 当前记录内容 | awk '{print $0}' |
$1~$n | 分别保存着当前记录的字段1到字段n的内容 | awk '{print $1, $2, $3}' |
FS | 字段的分隔符,默认是空格或Tab | awk 'BEGIN{FS=":"}{print $1,$3,$6}' /etc/passwd |
NF | 记录当前记录中的字段个数 | awk '{print $0} END{printf("Total Field(s):%d\n", NF)}' 201509.log |
NR | 已经读出的行数,从1开始计数;对于多个文件的情况下,该值会持续累加 | awk '{print NR}' 201508.log 201509.log |
FNR | 对于当前处理的文件来说,已经读出的行数;对于多个文件的情况下,该值是各个文件独自对应的行号 | awk '{print FNR}' 201508.log 201509.log |
RS | 输入的记录分隔符, 默认为换行符 | awk 'BEGIN{RS=" "}{print FNR}' 201508.log |
OFS | 输出字段分隔符, 默认也是空格 | awk 'BEGIN{OFS="\t"}{print $1, $2, $3}' 201509.log |
ORS | 输出的记录分隔符,默认为换行符 | 一般用的很少,此处不举例说明了 |
FILENAME | 当前输入文件的名字 | awk '{print FILENAME}' 201509.log |
入门第四步——操作符
这里主要总结awk中的关系操作符和布尔操作符,在两个表达式之间进行比较操作时,经常会用到这里总结的操作符。
操作符 | 描述 |
---|---|
< | 小于 |
> | 大于 |
<= | 小于等于 |
>= | 大于等于 |
== | 等于 |
!= | 不等于 |
~ | 匹配 |
布尔操作符
操作符 | 描述 |
---|---|
|| | 逻辑或 |
&& | 逻辑与 |
! | 逻辑非 |
由于操作符经常用于流程判断,在后面的流程判断中将结合这里总结的操作符进行实例分析。
入门第五步——格式化输出
我们使用awk的目的是什么?从复杂的数据中抽取出我们需要的数据,并展现出来,以便阅读。为了方便阅读,就需要输出的格式是可控的,是可自定义的。为此,在awk中也有一个和 C语言 一样的函数—— printf
。我们可以使用这个函数来进行输出格式控制。至于具体的控制选项,自己Google研究去吧。
入门第六步——参数传递
我们可以向 Shell 脚本传递参数,这样可以带来极大的扩展性和代码的复用,那么如何向awk脚本传递参数呢?
在awk中,参数将值赋给一个变量,这个变量可以在awk脚本中访问,具体的使用方式如下:
awk [POSIX or GNU style options] -f progfile var1=value1 var2=value2 file ... awk [POSIX or GNU style options] 'program' var1=value1 var2=value2 file ...
其中的var1为参数变量,value1为参数值;以此类推,var2为第二个参数变量,value2为第二个参数的值。这些变量必须要放在脚本的后面,待处理文件名的前面,而且在给变量赋值时,等号的两边不允许出现空格。这就是向awk传递参数的方法。
有的时候,我们在Shell脚本中会使用awk脚本,我们可以将Shell脚本的参数直接传递给awk脚本,例如:
awk -f script.awk "var1=$1" "var2=$2" file
其中 $1
和 $2
分别为Shell脚本的第一个参数和第二个参数;是的,我们可以直接这么传递参数,没有任何问题。
虽然我们可以向awk脚本中传递参数,但是有一个不幸的消息——我们传递的参数在BEGIN过程中是不可用的。也就是说,我们从命令行传递的任何参数值,在BEGIN过程中,都无法得到我们传递的值,直到首行输入完成以后,命令行参数才是可用的。所以呢,你不要想着写出类似下面的代码:
awk -f script.awk var=';' file
script.awk脚本
BEGIN{FS=var1}{....}
但是,解决问题的方法还是有的,如果你感兴趣,可以去看看 -v
选项。
进阶第一步——流程判断
awk中的流程判断和C语言几乎是一致的,这让熟悉C语言的人非常容易上手。
if (expression1) { action1 } else if (expression2) { action2 } else { action3 }
在上面总结了那么多操作符,这里重点对 ~
匹配操作符进行演示:
awk '{ if ($0 ~ /[Yy](es)?/) { print "Yes" } else if ($0 ~ /[Nn]o?/) { print "No" } else { print "No Match" } }'
当输入的记录匹配指定的正则表达式,则表达式的值为真,否则为假;这种匹配玩法在C中可是没有的哦。
在awk中还提供了一个三元操作符,这让我很吃惊,的确很方便:
expr ? action1 : action2
简单的说就是如果expr为真,就返回action1的执行结果,否则返回action2的执行结果。
awk '{ result = (var >= 60 ? "OK" : "FAILED") print result }' var=50
进阶第二步——循环
在awk中支持 while
、 do{...} while
和 for
循环。
# while循环 while (condition) { action } # do{...} while循环 do { action } while(condition) # for循环 for (i = 1; i < 10; ++i) { print i }
还是和C语言那样,awk中也有影响循环的 continue
和 break
关键字。
进阶第三步——数组
在awk中,所有的数组都是关联数组。关联数组的特点是它的下标可以是一个字符或一个数值。我们定义一个数组时,不必指明数组的大小,只需要为数组指定下标,然后赋值即可。比如:
awk '{ array[1]="HelloWorld" array["Jelly"]="JellyThink"; print array[1] print array["Jelly"] }'
下面创建一个数组,并使用一种类似高级语言中的枚举器的东西来打印数组:
awk '{ for (i = 1; i <= 10; ++i) { array[i] = i * 2 } for (value in array) { print array[value] } }'
有的时候,我们需要判断数组是否包含指定的下标,我们可以使用以下代码来实现:
awk '{ for (i = 1; i <= 10; ++i) { array[i] = i * 2 } if (2 in array) { print "FOUND" } else { print "NOT FOUND" } }'
进阶第四步——函数
函数的内容不少,请参见这篇《awk中的函数》。
总结
总结这篇文章花了好长的时间啊,总算把awk相关的东西都总结的差不多了,自己也算把awk又重新学习了一遍,希望我的这篇文章也能够帮助到你。
果冻想-一个原创技术文章分享网站。
2015年10月19日 于呼和浩特。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 玩玩区块链:概念
- 玩玩智能家居2:ESPEasy
- 玩玩JS设计模式之:发布/订阅
- [NodeJS][Golang] 玩玩看 Line Beacon
- 天天 CRUD 的我,也想玩玩高大上的 Prometheus
- IPv6闲谈-一起玩玩IPv6自动配置
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
MD5 加密
MD5 加密工具
RGB CMYK 转换工具
RGB CMYK 互转工具