《Linux Shell》之三:awk编程

awk是一种编程语言,gawk是目前最新的版本,当前的Linux版本用的都是gawk,利用gawk可以实现数据查找、抽取文件中数据、创建管道流命令等功能,awk实际是/bin/gawk的链接。

 

4.3.1 awk编程模型

awk程序由一个主输入循环main input loop维持,主输入循环反复执行,直到终止条件被触发,主输入循环自动依次读取输入文件行,以供处理,而处理文件行的动作是由程序员添加的。

awk还定义了两个特殊的字段:BEGIN和END,BEGIN用于在主输入循环之前执行,即在未读取输入文件之前执行,END则相反,用于在主输入循环之后执行,即在读取输入文件行完毕后执行。

 

4.3.2 awk调用方法

跟sed类似,也有三种方式:

① 在shell命令行直接输入命令调用,格式为:
awk [-F 域分隔符] 'awk程序段' 输入文件

② 将awk程序段插入脚本文件,然后通过awk命令调用它,格式为:

awk -f awk脚本文件 输入文件

③ 将sed命令插入脚本文件后,将该文件变成可执行,然后直接执行,不过需要一#!/bin/awk -f 开头

./awk脚本文件 输入文件

 

4.4.1 awk模式匹配

# awk '/^$/{print "this is a blank line."}' input  --> 打印空白行,遇到空白行就打印一句话

第三种调用方式,先写个src.awk脚本文件:

#!/bin/awk -f
/^$/{print "this is a blank line."}

 然后chmod u+x src.awk,然后./src.awk input即可

 

4.4.2 记录和域

awk将每个输入文件行定义为记录,行中每个字符串定义为域,域之间用空格、Tab键或其他符号进行分割,称之为分隔符。

$1表示第一个域,$2表示第二个域以此类推。注:$0表示所有的域

 

使用 -F 选项指定分隔符:

# awk -F "\t" '{print $3}' test.txt

还可以通过设置FS的值改变分隔符:

# awk 'BEGIN {FS=","} {print $1}' test.txt

# awk 'BEGIN {FS="\t+"} {print $1}' test.txt  --> 利用正则表达式设置分隔符

 

4.4.3 awk 关系和布尔运算符

运算符 意义
< 小于
> 大于
<= 小于等于
>= 大于等于
== 等于
!= 不等于
~ 匹配正则表达式
!~ 不匹配正则表达式

 

使用正则表达式~符号例子

# awk 'BEGIN {FS=":"} $1~/root/' /etc/passwd  --> 在/etc/passwd文件中第一个域匹配root的

# awk 'BEGIN {FS=":"} $0~/root/' /etc/passwd  -->  全部域匹配root的

# awk 'BEGIN {FS=":"} $0!~/nologin/' /etc/passwd  --> 全部域不匹配nologin的

 

awk的条件语句示例

# awk 'BEGIN {FS=":"} {if($3<$4) print $0}' /etc/passwd

# awk 'BEGIN {FS=":"} {if($3==10 || $4==10) print $0}' /etc/passwd

# awk 'BEGIN {FS=":";count=0} {if($3>99) count++} END {print count}' /etc/passwd  --> 列出系统中UID大于99的账号数量

 

4.4.4 表达式

+ - * / %(模) ^或**(乘方) ++x x++

# awk '/^$/ {print x+=1}' input

一个计算平均值的awk脚本:

#!/bin/awk -f
BEGIN {FS=","}
{
        total=$3+$4+$5
        avg=total/3
        print "result:" + avg
}

 

4.4.5 awk系统变量:

变量名 意义
$n 当前记录第 n个域,域间用FS分隔
$0 记录的所有域
ARGC 命令行参数的数量
ARGIND 命令行中当前文件的位置(以0开始标号)
ARGV 命令行参数数组
CONVFMT 数字转换格式
ENVIRON 环境变量关联数组
ERRNO 最后一个系统错误描述
FIELDWIDTHS 字段宽度列表,空格键分割
FILENAME 当前文件名
FNR 浏览文件的记录数
FS 字段分隔符,默认是空格
IGNORECASE 布尔变量,如果为真,忽略大小写
NF 当前记录中的域数量
NR 当前记录数,从1开始,表示当前是第几条记录
OFMT 数字的输出格式
OFS 输出域分隔符,默认是空格键
ORS

输出记录分隔符,默认是\n

RLENGTH 由match函数所匹配的字符串长度
RS 记录分隔符,默认是\n
RSTART 由match函数匹配的字符串的第1个位置
SUBSEP 数组下标分隔符,默认是\034

另外还有个RT:

RT就是当RS为正则表达式时的匹配到的每个记录的分割符的内容即为RT变量表示,用到RS的时候一般可以用RT作为每次匹配的分割符的变量的值,靠,这么解释你还不清楚就去面壁去吧。^_^

# echo "111 222a333 444b555 666"|awk 'BEGIN{RS="[a-z]+"}{print $1,RS,RT}'

当RT是利用RS匹配出来的内容。如果RS是某个固定的值时,RT就是RS的内容

示例:打印19000行至20000行

# awk '{if(NR>= 19900 && NR<=20000) print $0}' /data/log/resin/resin.log

判断log日志中通过日期的比较来打印:

# awk -F, 'BEGIN{now=systime();aa=strftime("%Y-%m-%d %H:%M:%S", now)}{if ($2 > aa) print $0}' input

# awk -F, 'BEGIN{now=mktime("2013 10 05 12 12 12 333");aa=strftime("%Y-%m-%d %H:%M:%S", now)}{if ($2 > aa) print $0}' input

 

4.4.6 格式化输出

awk使用类似于C语言的printf函数格式化输出

基本语法是:printf (格式控制符, 参数)

 # awk 'BEGIN {FS=","} {printf("%-15s\t%s\n",$1,$3)} ' input.txt

下面这个例子没有输入文件,直接打印一个数字,很神奇吧。

# awk 'BEGIN {printf("%-10.3f\n", 2009.12345)}'

 

4.4.7 内置字符串函数

字符串替换、查找、分割功能,有很多内置函数,let me see see:

函数名 意义
gsub(r,s) 在整个$0中用s替换r
gsub(r,s,t) 在t中用s替换r,t可以是字符串也可以是域
index(s,t) 返回s中第一个字符串t的位置,从1开始
length(s) 返回s的长度
match(s,t) 测试s是否包含匹配t的字符串,t可以是正则式
split(r,s,t) 在t上利用分割正则式r分割成序列s
sub(r,s,t) 将t中第一次出现的r替换成s
substr(r,s) 返回字符串r中从s开始的后缀部分
substr(r,s,t) 返回字符串r中从s开始长度为t的后缀部分

 

解释下

gsub函数执行字符串替换功能,它将第一个字符串替换成第二个字符串。gsub函数有两种形式,第一种形式作用于全部域$0,也就是gsub(r,s)。第二种形式作用于域t,也就是gsub(r,s,t)

// 第一条命令:替换第1域上的root字符串,注意我的输出的分隔符设定OFS

# awk 'BEGIN {FS=":"; OFS="+"} {gsub(/root/,"haha",$1); print $0}' /etc/passwd

# awk 'BEGIN {FS=":"; OFS="+"} gsub(/root/,"haha",$1) {print $0}' /etc/passwd

上面两行的席位区别,然后输出结果是:第一个命令会输出所有行,第二个只会输出gsub找到的那行,why

 

// index和length函数的用法

# awk 'BEGIN {print index("google.com", ".com")}'

# awk 'BEGIN {print length("google.com")}'

 

match(s,t)测试s是否包含正则表达式t字符串,若匹配成功,则返回t的首位置,若不成功,则返回0

# awk 'BEGIN {print match("grade one", /D/)}'

# awk 'BEGIN {IGNORECASE=1; print match("grade one", /D/)}'

 

// sub(r,s,t) 只是将t中第一次出现的r替换成s

# awk 'BEGIN {str="multiprocessors program"; sub(/pro/, "PRO", str); printf("%s\n", str)}'

 

// substr两个函数示例:

# awk 'BEGIN {str="multiprocessor programming"; print substr(str,6)}'

# awk 'BEGIN {str="multiprocessor programming"; print substr(str,6,9)}'

 

4.4.8 向awk脚本传递参数

 格式为: awk脚本文件  param1=value1 param2=value2  输入文件

其中param可以是自定义变量,也可以是系统变量

注意:命令行参数不能被BEGIN中的语句访问到

 

4.4.9 条件语句和循环语句

这个跟C语言语法是一样的,不多做介绍了

# if (x==y) print x

# if (x ~ /[Hh]el?o) print x

while(cond) action

do action while(cond)

for (i=0; i<10; i++) print i

 

4.4.10 数组

array[index]=value

awk数组无需定义数组类型和大小,赋值后就能使用了,哦也。

 

1. 关联数组

关联数组其实就是一个Map,实际上awk数组就是一个Map,array[0]=12表示用字符串0为key,12为value

awk所有数组都是关联数组,array[09]跟array[9]完全不一样

基于上述的关联数组的含义,awk定义了一组foreach循环,而且很特殊的,跟java中还不一样:

for (var in array) 

   print array[var]

实际上var就相当于Map中的key,这样可以理解了吧,^_^

还有个条件表达式,相当于判断Map是否含有这个key值:

if (index in array) print "yes"

 

2. split函数

终于要到压轴大戏也就是大名鼎鼎的split函数了

split(r,s,t):将字符串r以t为分隔符拆分成字符串数组,存放在s中。 并返回数组s的大小

# awk 'BEGIN {print split("abc/def/ghi",str,"/")}'

# awk 'BEGIN {FS=","} {print split($1,name," ")}' input.txt

再来看一下一个小小脚本:

#!/bin/awk -f
BEGIN {FS=","}
{
	split($1,names," ");
	for (i in names) print name[i]
}

 

3. 数组形式的系统变量

ARGV和ENVIRON两个

ARGC是ARGV数组中元素的个数

# awk -f argv.awk xyz n=99 "Hello World"

结果打印: awk,xyz,n=99,,Hello World

ENVIRON变量存储了Linux系统的环境变量

例如:

awk 'BEGIN {for(i in ENVIRON) print(i" = "ENVIRON[i])}'

 

 

4. awk 执行系统命令:

ls -l -c --time-style="+%Y-%m-%d" | awk '{if($6 == "2013-07-01") system("rm -rf " $7)}'

不小心在当前页面新建了很多文件,怎样马上删除呢?就是上面的例子

本人博客已搬家,新地址为:http://www.pycoding.com/

猜你喜欢

转载自yidao620c.iteye.com/blog/1881809