内容简介:那是难堪的一幕,记得那是一次面试,面试官问我:如何查看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自动配置
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Game Programming Patterns
Robert Nystrom / Genever Benning / 2014-11-2 / USD 39.95
The biggest challenge facing many game programmers is completing their game. Most game projects fizzle out, overwhelmed by the complexity of their own code. Game Programming Patterns tackles that exac......一起来看看 《Game Programming Patterns》 这本书的介绍吧!