Chinaunix首页 | 论坛 | 博客
  • 博客访问: 135357
  • 博文数量: 28
  • 博客积分: 527
  • 博客等级: 中士
  • 技术积分: 367
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-09 17:05
个人简介

运维开发工程师。致力于网络,WEB应用服务,Linux系统运维。方向:操作系统,监控,自动化

文章分类

全部博文(28)

文章存档

2013年(12)

2012年(16)

分类: Python/Ruby

2012-12-15 19:44:19

导论:awk的世界
严格地说,awk应该算是独立于shell之外的一门编程语言了。由于开发人员能够在shell command line内直接编写awk代码,并整合到管道中,因此,awk和shell整合地非常紧密,变成了一项标准unix tool。awk尤其擅长处理文本设计、编排报表的工作,常常会带给你意外的惊喜。以下awk是linux的gawk版本摘要,awk遵循POSIX规范。
注:以下的记录(record)指一行数据,字段(field)指记录中以分隔符分开的一段数据。


基础语法
  1. awk options 'pattern { action }' input-files
  2. #或
  3. awk options -f awk-scripting-file input-files
awk一次读取输入流(文件或标准输入)的一条记录(record,通常是行),针对每个成功匹配这一行的模式(模式值为真),awk执行对应的action,且所有模式都会针对每条记录检查。如此往复,直到EOF。所以pattern和action暗含了一个if..then..的控制结构。
  • pattern部分用来定位具体需要action的行:几乎是任何表达式,一般情况下,是一个ERE正则。pattern省略后,即action对每一行都会生效。
  • actions部分用来执行具体操作:为任何awk语句,一般情况下,是一个print $n语句。action省略后,action即{ print },即打印整行。


awk内建变量
$0 当前整条输入(行)记录,不包括RS(\n)字符;
$n 当前行记录的第n个字段,字段间用FS分隔;
ARGC 命令行参数的数目
ARGV 命令行参数的数组
ARGIND 当前文件的位置(从0起始)
CONVFMT 数字转换格式(默认%.6g);
ENVIRON 调用awk的环境变量hash数组;
ERRNO 系统错误描述;
FIELDWIDTHS 字段宽度列表(用空格分隔);
FILENAME 当前文件名;
IGNORECASE 若为真,则忽略大小写匹配;
OFMT : 数字输出格式(默认%.6g);
OFS 输出字段分隔符,默认是一个空格字符,output field separator
ORS 输出记录分隔符,默认是换行符,output record separator
RS 输入记录分隔符,默认是换行符,record separator
FS 输入字段分隔符(默认是任意空格、\t字符组合),field separator,可以设置成/ERE REGEX/;
FNR 当前处理中的文件的当前行数;
NR 当前总记录数, number of records
NF 当前记录中的字段数, number of fields
返回值0 表示失败,假
返回值非0 表示成功,真


注释和空白
  • 注释为#符号开头,类比shell;
  • 任何地方允许空白,单条语句不能多行,除非结尾有'\',类比shell;


标量:字符串和数字
awk标量存放字符串和数字,赋值即声明,变量大小写敏感。
  • awk字串最短为空,长度为0,最长无限制,视内存而定。
  • 字串的比较用"==,!=,<,>,>="比较,非零为真,短的字符串总<长字符串,如"a"<"aa"返回1;
  • awk没有特殊的字符串接续操作符,类比shell,两个字串按照字面的排列合在一起。
  • awk用一律在内部用双精度浮点数表示任何数值;
  1. #对于第三点,字符串相连
  2. $ awk 'BEGIN{ s="ab" "cd" ; q="xy" "z" ; s=s q ; print s }'
  3. abcdxyz #这里s q空格必须


正则
awk使用ERE(扩展posix正则表达式),用""或//圈引。
ERE基本元素:
\ 转义序列
. 任意字符(不含换行符)
* 重复前一个正则匹配0到多次
^ 锚点,行开头
$ 锚点,行结尾
[..] 字符集,只一次匹配字符集中的一个字符;注意[^..]代表匹配不在字符集里的字符;
{n,m} 重复n到m次,必须显示指定awk --posix或--re-interval开启重复n次支持
+ 匹配一个或多个
? 匹配一个或0个
| 匹配或
(..) 分组,以便\n后向引用
\w [[:alnum:]_],同perl里的\w
\W \w的取反
\< 单词开头
\> 单词结尾
\b 单词结尾或开头,\<,\>的结合;


数组
awk支持关联数组,也就是说,键可以是数值,可以是字符串表达式。关联数组也叫哈希(hash)。
awk的数组索引从1开始。
awk的数组是自动创建,自动增长的;
awk数组的元素间独立,不必要为统一类型;
数组名和变量名的命名空间是重合的(不像perl),因此变量名和数组名应避免冲突;
awk支持多维数组,awk_set[ v1, v2, v3 ]确定一个三维数组元素;


命令行参数
ARGC是参数索引计数,ARGV是参数数组,通过遍历,能够取得awk命令上的参数(不包含选项)

  1. $ awk -v external_var=ext 'BEGIN { for (k=0;k
  2. print "ARGV["k"] is "ARGV[k] }' s*
  3. ARGV[0] is awk
  4. ARGV[1] is sed.sh
  5. ARGV[2] is standardio.sh
  6. ARGV[3] is subprocess.sh
  7. #可见第一个参数ARGV[0]始终是awk程序,选项-v没有计入参数列表中。

环境变量
环境变量存在ENVIRON关联数组里,只读就满足一般需求了。
  1. $ awk 'BEGIN{ print ENVIRON["LANG"] ; print ENVIRON["SHELL"] }'
  2. en_US
  3. /bin/bash

模式pattern
视模式为测试条件,用以匹配当前行,若模式表达式的值为真(非0),则为真,执行action代码。
模式有几种形式:
BEGIN #begin模式
END #end模式
NF == 0 #空记录
NR < 5 #选定第1-4行的文本域处理
( FNR == 3 ) && ( FILENAME ~ /[.][ch]$/ #如果当前文件是c源代码文件,且选定第3行;
$1 ~ /jack/ #第1个字段匹配jack
$2 !~ /jack/ #第2个字段不匹配jack
/^\w*$/ #等效于$0 ~ /regex/,全由单词字符组成的行;
( FNR == 3 ) , ( FNR == 10 )  #范围表达式,选定第3-10行的文本域;

BEGIN和END
BEGIN和END是两种特殊模式,这些块,即对应的action,只运行一次。BEGIN和对应的块通常放在起始处,做变量初始化;END和对应的块通常放在结尾,做统计结果输出和脚本清理;
注意:如果BEGIN模式后不包括其他操作,awk会退出,不必要读取任何输入和文件。
  1. #在BEGIN块里实现FS和OFS的全局设置;
  2. #取用户名和用户登录shell并用*号隔开打印
  3. #!/bin/bash
  4. awk ' BEGIN{#这里的BEGIN是模式,{}是action
  5. FS=":"
  6. OFS="*"
  7. }
  8. { print $NF , $1 } //逆序
  9. END{
  10. #comment here
  11. }' /etc/passwd


操作action
操作由{}圈引,awk语句在一行内用";"隔开,不同行时不用加";"。
最常见的操作:print。一个print语句里包含了以逗号分隔的0或多个表达式,输出字段分隔字符由OFS指定,print结束后以ORS结尾输出。
  1. #把单词列表逐行输出
  2. $ echo who is my love | awk '{ OFS = "\n" ; $1 = $1 ; print }' #$1=$1必须,至少指定一个字段的值,否则$0不会变
  3. who
  4. is
  5. my
  6. love
  7. -------------------------
  8. #打印全文,同cat file
  9. awk '{ print $0 }' file
  10. awk '{ print }' file


记录和域
  • 记录record:由RS分隔的字串称为一条记录,RS默认为\n,即一条记录为一行,$0代表整条记录;NR为当前行的行数(总文件范围下的);
  • 域field:一条记录的每个字段叫“域”,NF为当前域(字段)的个数;输入域分隔符是FS,默认情况下是\t和空格的组合,可通过-F命令行或FS赋值指定。域的引用:$1,$2,...,$NF。
1.选取若干指定的列:$n
  1. #产出PID和进程命令(ps -ef的第2列和最后一列)
  2. ps -ef | awk '{ print $2 , $NF }'
2.设置字段分隔字符:FS和字段输出字符:OFS
-v 传递shell变量给awk程序
  1. #取用户名和用户登录shell
  2. awk -F':' '{ print $1,$NF }'
  3. 进阶
  4. awk -v FS=":" -v OFS="<->" '{print $1 ,$2,$NF}' /etc/passwd
  5. root<->x<->/bin/bash
  6. bin<->x<->/sbin/nologin
  7. daemon<->x<->/sbin/nologin
  8. -----------------------------------
  9. $ echo | awk -v 'a=yes' '{print a }'     
  10. yes


awk控制语句(k&r风格)
判断
  1. if ( expression1 ){
  2. statement1
  3. }else if ( expression2 ){
  4. statement2
  5. }else{
  6. statement3
  7. }
循环
while循环
  1. while ( expression ){
  2. statement
  3. break #可选
  4. }
for循环
  1. for( i=1 ; i<=5 ; i++ ){
  2. statement
  3. break|continue #不支持多层break
  4. }
for循环遍历关联数组
  1. $ awk 'BEGIN{
  2. hash["a"]=1
  3. hash["b"]=2
  4. hash["c"]=3
  5. for ( name in hash ){ #name相当于perl里的for $_ (keys %hash)
  6. print name "=>" hash[name]
  7. }
  8. }'
  9. a=>1
  10. b=>2
  11. c=>3
awk测试数组成员
  1. awk 'BEGIN{
  2. hash["a"]=1
  3. hash["b"]=2
  4. hash["c"]=3
  5. if( "d" in hash ){
  6. print "in"
  7. }else{
  8. print "out"
  9. }
  10. }'
  11. --------------------------------
  12. #而非
  13. if ( hash["d"] != "" ) ... #这样会引用hash["d"],使之立即初始化为空值数组元素。


awk引用shell命令,引用shell变量、IO重定向及其他
1. 管道 + getline 实现
需要注意getline函数>0时,成功;
需要及时关闭管道文件;
用while处理时,管道命令运行一次,getline每次读取一行;
 $ awk 'BEGIN{
        cmd = "date" #使用变量保存管道
        if ( ( cmd | getline var ) > 0 ){ #getline从外部命令cmd的结果里读取一行,存入var
#测试getline返回的是否>0,成功
                print "The date is " var
        }
        close(cmd) #建议关闭管道文件
 }'
 The date is Mon Dec 17 22:41:57 CST 2012
---------------------------------------------
 $ awk 'BEGIN{   
        cmd = "cat txt ; rm txt" #测试该命令是否在while测试条件中只运行了一次,若为 多次,rm会报错
        while ( ( cmd | getline var ) > 0 ){ #一次读取一行,getline执行多次,cmd只执行一次
#引用shell命令的标准形式
                print ">>>" var
        }
        close(cmd)
 }'

2. system()实现
  1. $ awk 'BEGIN{
  2. cmd = "uptime"
  3. if ( system( cmd ) != 0 ){ #推荐加调用判断
  4. print "system call bad"
  5. }}'
  6.  23:08:21 up 151 days,  3:02,  5 users,  load average: 0.00, 0.00, 0.00
3. IO重定向
可以在print和printf后加入>或>>重定向语句,也可以在getline后加入<语句,一旦使用完毕,须关闭文件句柄 close(fd)
  1. $ awk '{ print $0 > "youtube.txt" END{ close("youtube.txt") }' plain.txt
  2. $ cat youtube.txt
  3. send someone to love me
  4. i need to rest in arms
  5. ...
4. 引用变量
-v 传递shell变量给awk程序 或 "'$shell_var'"
  1. $ awk -v awk_var=$you 'BEGIN { print awk_var }'
  2. me
  3. $ awk 'BEGIN { print "'$you'" }'             
  4. me

awk字符串函数
字长:length( str )
  1. $ echo "Legend" | awk '{ print length($0) }'
  2. 6
子串提取:substr( str , start_index , len )
  1. echo "Legend" | awk '{ print substr($0,4,3) }' #好吧,index是从1开始的
  2. end
大小写转换:toupper(str ), tolower( str )
  1. $ echo "Legend" | awk '{ print toupper($0) }'
  2. LEGEND
  3. $ echo "Legend" | awk '{ print tolower($0) }'
  4. legend
找字串:index( str )
若找不到,返回0,表示失败;
  1. $ echo "Legend" | awk '{ print index($0,"ge") }'
  2. 3 #返回ge的起始index位置
匹配正则match( str , regex )
若匹配,返回匹配string的index位置,不匹配为0
  1. $ echo "I'm the Legend" | awk --re-interval '{ #巨坑:awk不支持{n,m}的ere,必须制定--posix或--re-interval才能使用{n,m}的正则
  2. print match($0,/[a-z]{3}/)
  3. print substr($0,RSTART,RLENGTH )
  4. }'
  5. 5
  6. the
替换:sub( regexp, replacement, target )与gsub(globlly substitution),sub和gsub直接修改target字串。
  1. $ awk 'BEGIN{
  2. string = "champion12sh3ip"
  3. print string
  4. sub( /[0-9]/ , "" , string)
  5. print string
  6. }'
  7. champion12sh3ip
  8. champion2sh3ip
  9. --------------------
  10. $ #将sub换成gsub
  11. champion12sh3ip
  12. championship
  13. --------------------
  14. $ gsub( /[0-9]/ , "&&" , string) #特殊用法,&为regex匹配的文本段,&&为重复两遍
  15. champion1122sh33ip
分割字符串:int split( string, array, regexp )
当regexp省略时,默认使用FS的值 ,split()返回array长度。
  1. $ awk 'BEGIN{
  2. string = "yes I am what i am " #最后的空白省略了,和FS处理时一样
  3. len = split( string , array )
  4. print len
  5. for( i=1 ; i<=len ; i++){
  6. print ">" array[i]
  7. }
  8. }'
  9. 6
  10. >yes
  11. >I
  12. >am
  13. >what
  14. >i
  15. >am
  16. ---------------------------------------------
  17. $ awk 'BEGIN{
  18. string = "money:is:very:bad:" #不会省略最后一个":"
  19. len = split( string , array , ":")
  20. print len
  21. for( i=1 ; i<=len ; i++){
  22. print ">" array[i]
  23. }
  24. }'
  25. 5
  26. >money
  27. >is
  28. >very
  29. >bad
  30. >
  31. -----------------------------------------------
  32. $ awk 'BEGIN{ #把字串每一个字符提出的好方法
  33.         string = "I am what i   am"
  34.         len = split( string, chars, "" )
  35.         for ( idx = 1 ; idx <= len ; idx++ ) {
  36.                 print chars[idx]
  37.         }
  38. }'
  39.  $
  40. i$
  41. ^I$
  42. a$
  43. m$
  44. ----------------------------------------
  45. $ split("", array) #清空数组
字串格式化printf,sprintf
printf将字符串和数字格式化为所需要的格式,printf()打印字串到屏幕,sprintf()将格式化后的字串保存到变量。
对齐操作:
% width.precision format-specifier
  1. $ awk 'BEGIN{
  2. a="i am the"
  3. b="99"
  4. c="C"
  5. printf("%s : %d,%c\n", a, b, c)
  6. d=sprintf("%s-%d-%c",a,b,c)
  7. print d
  8. x=-7.58
  9. printf "%010.5f\n", x
  10. }'
  11. i am the : 99,C
  12. i am the-99-C
  13. -007.58000



awk数值函数和随机数
awk提供了大部分常用的数值函数,包括cos(x),log(x),int(x)等,其中随机数rand()是一个比较tricky的地方,rand()每次调用返回的都是相同的[0,1)区间的"随机数",因此需要前置srand(),把当前时间设为种子,才能保证每次rand()出来的尽可能随机(伪随机)。
  1. #这个例子实现了给定n,m,输出这个区间的随机整数。
  2. awk '
  3. BEGIN{
  4. myrand = gen_random(10,20)
  5. print myrand
  6. }
  7. function gen_random( low , high ){ #函数可以在任意成对的模式/操作组前后之间定义
  8. low = int( low )
  9. high = int( high )
  10. if( low >= high ){ #health check
  11. return low
  12. }
  13. srand()
  14. n = low + int( rand() * ( high + 1 - low ) ) #因为取整,+1必须,0<=rand()<1
  15. return n
  16. }
  17. '
  18. $ for((i=1;i<=100;i++)); do sh awk.sh; sleep 1; done
  19. 17
  20. 19
  21. 12
  22. 18
  23. 16
  24. 17
  25. ...


awk实例
说完了一些基本概念,让我们从一些例子体验一下awk,某些例子前面已经举过。
1. awk打印非空行
  1. $ awk 'NF > 0' file
  2. $ awk 'NF > 0 { print $0 }' file #等价形式
  3. -----------------------------------
  4. $ awk '{ print }' $file  #同cat file
2. awk转置单词列表到逐行输出
  1. echo $list | awk '{ OFS="\n" ; $1=$1 ; print $0 }'
3. wc的一种实现
  1. $ awk '{count+=length($0)+1;word+=NF } END{ print NR , word , count }' plain.txt
  2. #length($0)计算整个行的字符数,+1算上换行符;
4. 计算某一列的平均值(假设第一行不是字段说明)
  1. $ awk 'BEGIN{ col=N } { sum += $col } END{ print sum/NR }' file #N是用户定义的列索引值
5. grep的一种实现
  1. $ grep -E 'pattern' files #ERE
  2. $ awk '/pattern/ { print FILENAME ":" FNR ":" $0 }' files #注意要用FNR,不是NR
6. dos2unix的3种实现
  1. $ awk 'BEGIN{ RS="\r\n" } { print }' windows_format.txt > unix_format.txt #指定RS值
  2. $ sed 's/\r$//' windows_format.txt  > unix_format.txt #sed用法
  3. tr -d '\r' < windows_format.txt  > unix_format.txt #tr用法
  4. $ dos2unix -n windows_format.txt unix_format.txt #dos2unix用法
7. 把字串每一个字符提出,放到数组里
  1. $ awk 'BEGIN{
  2. string = "I am what i am"
  3. len = split( string, chars, "" )
  4. for ( idx = 1 ; idx <= len ; idx++ ) {
  5. print chars[idx]
  6. }
  7. }'



参考资料
http://www.ibm.com/developerworks/cn/linux/shell/awk/awk-1/index.html
http://www.ibm.com/developerworks/cn/linux/shell/awk/awk-2/
http://www.ibm.com/developerworks/cn/linux/shell/awk/awk-3/
http://www.cnblogs.com/serendipity/archive/2011/08/01/2124118.html
http://blog.sina.com.cn/s/blog_5d22d9b40100jer4.html

阅读(1692) | 评论(0) | 转发(0) |
0

上一篇:010-截取列字段cut

下一篇:源码安装ruby

给主人留下些什么吧!~~