awk
从文本文件和字符串中抽取信息。为获得所需信息,文本必须格式化,意即用域分隔符划分抽取域,分隔符可能是任意字符
1 调用awk
有三种方式调用awk,第一种是命令行方式,如:
awk [-F field-separator] 'commands' inut-file(s)
这里,commands是真正的awk命令。[-F 域分隔符]是可选的,awk使用空格作为缺省的域分隔符,因此如果要浏览域间有空格的文本,不必指定这个选项,否则必须指明- F选项,如:
awk -F: 'commands' input-file
第二种方法是将所有awk命令插入一个文件,并使awk程序可执行,然后用awk命令解释器作为脚本的首行,以便通过键入脚本名称来调用它。
第三种方式是将所有的awk命令插入一个单独文件,然后调用:
awk -f awk-script-file input-files
-f 指明在文件awk-script-file中的awk脚本, input-file(s)是使用awk进行浏览的文件名。
2 awk脚本
2.1 模式和动作
任何awk语句都由模式和动作组成。在一个awk脚本中可能有许多语句。模式部分决定动作语句何时触发及触发事件。处理即对数据进行的操作。如果省略模式部分,动作将时刻保持执行状态。
模式可以是任何条件语句或复合语句或正则表达式。模式包括两个特殊字段BEGIN和END。使用BEGIN语句设置计数和打印头。BEGIN语句使用在任何文本浏览动作之前,之后文本浏览动作依据输入文件开始执行。END语句用来在awk完成文本浏览动作后打印输出文本总数和结尾状态标志。如果不特别指明模式, awk总是匹配或打印行数。
实际动作在大括号{ }内指明。动作大多数用来打印,但是还有些更长的代码诸如if和循环(looping)语句及循环退出结构。如果不指明采取动作, awk将打印出所有浏览出来的记录。
2.2 域和记录
awk执行时,其浏览域标记为$1,$2 . . . $n。这种方法称为域标识。注意这里用逗号做域分隔。使用$0,表示所有域。Awk浏览时,到达一新行,即假定到达包含域的记录末尾,然后执行新记录下一行的读动作。
为打印一个域或所有域,使用print命令。这是一个awk动作(动作语法用{}括起来)。
example:假设grade.txt使用空格作为域分隔符
2.2.1
$awk '{print $0}' grade.txt
$awk '{print $1,$3}' grade.txt
2.2.2 打印报告头
打印信息头放置在BEGIN模式部分,在awk查看第一条记录前,信息头被打印。\t是tab键
$awk 'BEGIN {print "name
belt\n------------------------"}{print $1"\t"$3}' grade.txt
2.2.3 打印信息尾
如果在末行加入end of report信息,可使用END语句。END语句在所有文本处理动作执行完之后才被执行。END语句在脚本中的位置放置在主要动作之后。
$awk 'BEGIN {print "name
belt\n----------"}{print $1"\t"$3} END {"end-of-report"}' grade.txt
2.2.4 几个简单规则
。 确保整个awk命令用单引号括起来。
。 确保命令内所有引号成对出现。
。 确保用花括号括起动作语句,用圆括号括起条件语句。
2.3 awk中正则表达式
2.3.1 awk中正则表达式用斜线括起来。如 /back/
元字符:\ ^ $ . [] | () * + ?
其中:
+ 使用+匹配一个或多个字符。
? 匹配模式出现频率。例如使用/XY?Z/匹配XYZ或YZ。
2.3.2 条件操作符
操作符
描述
操作符
描述
<
小于
>=
大于等于
<=
小于等于
~
匹配正则表达式
==
等于
!~
不匹配正则表达式
!=
不等于
关系操作符
&&
AND: 语句两边必须同时匹配为真。
||
OR: 语句两边同时或其中一边匹配为真。
!
非求逆
2.3.3 example:假设grade.txt使用空格作为域分隔符
,注意两种写法
$awk '{if($2 ~ /back/) print $0 }' grade.txt
(即如果field2包含back,打印匹配记录行)
$awk '$0 ~ /back/' grade.txt
$awk '$3=="48" {print $0}' grade.txt
(精确匹配--域$3精确匹配"48")
$awk '{if($3=="48") print $0}' grade.txt
(同上一句)
$awk '$0 !~ /back/' grade.txt
(不匹配)
$awk '$2 != "back2" {print $0}' grade.txt
(不等于)
$awk '{if ($6 < $7) print $1 }' grade.txt
$awk '$1 ~ /^...a/' grade.txt
(匹配field1的第四字符为'a')
$awk '$0 ~ /(Yellow|Brown)/' grade.txt
(匹配所有含Yellow或Brown的记录)
$awk '{if ($1=="back" && $2 =="hehe") print $0}' grade.txt
$awk '{if ($1=="back" || $2 =="hehe") print $0}' grade.txt
$echo '0145672' '0234523' |awk '{if ($1>$2) print $1 ;else print $2}'
(比较两个字符串的大小)
$db2 get db cfg for ksdbs |grep "First active log file" |awk '{print $6}'
(取日志文件名
S0169189.LOG)
2.4 awk内置变量
2.4.1
awk有许多内置变量用来设置环境信息。这些变量可以被改变。
ARGC
命令行参数个数
ARGV
命令行参数排列
ENVIRON
支持队列中系统环境变量的使用
FILENAME
awk浏览的文件名
FNR
浏览文件的记录数
FS
设置输入域分隔符,等价于命令行- F选项
NF
浏览记录的域个数
NR
已读的记录数
OFS
输出域分隔符
ORS
输出记录分隔符
RS
控制记录分隔符
ARGC支持命令行中传入awk脚本的参数个数。
ARGV是ARGC的参数排列数组,其中每一元素表示为ARGV[n],n为期望访问的命令行参数。
ENVIRON 支持系统设置的环境变量,要访问单独变量,使用实际变量名,例如ENVIRON[“EDITOR”]=“Vi”。
FILENAME支持awk脚本实际操作的输入文件。因为awk可以同时处理许多文件,因此如果访问了这个变量,将告之系统目前正在浏览的实际文件。
FNR支持awk目前操作的记录数。其变量值小于等于NR。如果脚本正在访问许多文件,每一新输入文件都将重新设置此变量。
FS用来在awk中设置域分隔符,与命令行中- F选项功能相同。缺省情况下为空格。如果用逗号来作域分隔符,设置FS = ","。
NF支持记录域个数,在记录被读之后再设置。
OFS允许指定输出域分隔符,缺省为空格。如果想设置为#,写入OFS = " # "。
ORS为输出记录分隔符,缺省为新行(\n)。
RS是记录分隔符,缺省为新行(\n)。
2.4.2 example
$awk 'END {print NR}' grade.txt
$awk '{print NF,NR,$0} END {print FILENAME}' grade.txt
$awk '{if (NR >0 && $4~/back/) print $0}' grade.txt
$pwd |awk -F/ '{print $NF}'
2.5
awk操作符
在awk中使用操作符,基本表达式可以划分为数字型、字符串型、变量型、域及数组元素,前面已经讲过一些。下面列出其完整列表。
= += *= / = %= ^ =
赋值操作符
?
条件表达操作符
|| && !
并、与、非
~
!~
匹配操作符,包括匹配和不匹配
< <= == != >=
关系操作符
+ - * / % ^
算术操作符
++
--
前缀和后缀
2.5.1 设置输入域到域变量名,一般的变量名设置方式为name=$n
$awk '{name=$1;belts=$4;if(belts ~/Yellow/) print name "is belt" belts}' grade.txt
2.5.2 域值比较操作
有两种方式测试一数值域是否小于另一数值域。
1) 在BEGIN中给变量名赋值。
2) 在关系操作中使用实际数值。
通常在BEGIN部分赋值是很有益的,可以在awk表达式进行改动时减少很多麻烦。使用关系操作必须用圆括号括起来。
例:
$awk '{if($6 < 27) print $0}' grade.txt
$awk 'BEGIN {BASELINE="27"} {if($6
2.5.3 修改数值域取值,修改文本域
当在awk中修改任何域时,重要的一点是要记住实际输入文件是不可修改的,修改的只是保存在缓存里的awk复本。awk会在变量NR或NF变量中反映出修改痕迹。
为修改数值域,如:$1=$1+5,会将域1数值加5,要确保赋值域其子集为数值型。
修改文本域即对其重新赋值。需要做的就是赋给一个新的字符串。字符串要使用双秒号(""),并用圆括号括起整个语法。
例:
$awk '{if($1=="back") $6=$6+1; print $1,$6,$7}' grade.txt
$awk '{if($1=="back") ($1="backtest"); print $1,$2}' grade.txt
$awk '{if($1=="back") {($1="backtest"); print $1,$2}}' grade.txt
(只打印满足条件的记录)
2.5.4 数值型域的计算
$awk '(tot+=$6); END {print tot}' grade.txt
(打印所有记录及$6的累加结果)
$awk '{(tot+=$6)}; END {print tot}' grade.txt
(只打印$6的累加结果)
2.5.5 文件长度相加
在目录中查看文件时,如果想快速查看所有文件的长度及其总和,但要排除子目录,使用ls -l命令,然后管道输出到awk,awk首先剔除首字符为d (正则表达式:^[^d])的记录,然后将文件长度列相加,并输出每一文件长度及在END部分输出所有文件的长度。
ls -l 的结果中一般文件长度是第5列,文件名是第9列。
以下命令使用此模式打印文件名及其长度,然后将各长度相加放入变量tot中:
$ ls -l | awk '/^[^d]/ {print $9"\t"$5} {tot+=$5} END {print "Total KB:" tot}'
2.6 内置的字符串函数
gsub(r,s)
在整个$0中用s替代r
gsub(r,s,t)
在整个t中用s替代r
index(s,t)
返回s中字符串t的第一位置
length(s)
返回s长度
match(s,r)
测试s是否包含匹配r的字符串
split(s,a,fs)
在fs上将s分成序列a
sprint(fmt,exp)
返回经fmt格式化后的exp
sub(r,s)
用$0中最左边最长的子串代替s
substr(s,p)
返回字符串s中从p开始的后缀部分
substr(s,p,n)
返回字符串s中从p开始长度为n的后缀部分
gsub函数有点类似于sed查找和替换。它允许替换一个字符串或字符为另一个字符串或字符,并以正则表达式的形式执行。第一个函数作用于记录$0,第二个gsub函数允许指定目标,然而,如果未指定目标,缺省为$0。
index(s,t)函数返回目标字符串s中查询字符串t的首位置。
length函数返回字符串s字符长度。
match函数测试字符串s是否包含一个正则表达式r定义的匹配。
split使用域分隔符fs将字符串s划分为指定序列a。
sprint函数类似于printf函数,返回基本输出格式fmt的结果字符串exp。
sub(r,s)函数将用s替代$0中最左边最长的子串,该子串被(r)匹配。
substr(s,p)返回字符串s在位置p后的后缀。
substr(s,p,n)同上,并指定子串长度为n。
2.6.1 gsub
在且仅在匹配的记录中替换一个字符串为另一个,使用正则表达式格式, /目标模式/,替换模式.
$ awk 'gsub(/4096/,40960) {print $0}' grade.txt
(数字替换)
$ ls -l | awk 'gsub(/system/,"system0") {print $0}'
(字符替换)
2.6.2 split
使用split返回字符串数组元素个数。工作方式如下:如果有一字符串,包含一指定分隔符- ,例如AD2-KP9-JU2-LP-1,将之划分成一个数
组。使用split,指定分隔符及数组名。此例中,命令格式为( "AD2-KP9-JU2-LP-1",parts_array,"-"),split然后返回数组下标数,这里结果为4。
$awk 'BEGIN {print split("123#456#4678",myarray,"#")}'
2.6.3 example
$ awk 'BEGIN {print index("trousers","er")}'
$ awk '$1=="J.Troll" {print length($1)" "$1}' grade.txt
$ awk 'BEGIN {print length("A FEN GOOD MEN")}'
$ awk 'BEGIN {print match("ABCD",/C/)}'
$ awk '$1=="LULU" {print match($1,"U")}' grade.txt
$ awk '$1=="L.Tansley" {print substr($1,1,5)}' grade.txt
$ awk '{print substr($1,3)}' grade.txt
$ awk 'BEGIN {STR="A FEN GOOD MAN"} END {print substr(STR,7)}' grade.txt
2.7
字符串屏蔽序列
\b 退格键
\t
tab键
\f 走纸换页
\ddd
八进制值
\n 新行
\c
任意其他特殊字符,例如\\为反斜线符号
\r 回车键
2.8 awk输出函数printf
每一种printf函数(格式控制字符)都以一个%符号开始,以一个决定转换的字符结束。转换包含三种修饰符。
printf函数基本语法是printf([格式控制符],参数),格式控制字符通常在引号里。
2.8.1 printf修饰符
awk printf修饰符
-
左对齐
Width
域的步长,用0表示0步长
.prec
最大字符串长度,或小数点右边的位数
awk printf格式
%c
ASCII字符
%d
整数
%e
浮点数,科学记数法
%f
浮点数,例如(123.44)
%g
awk决定使用哪种浮点数转换e或者f
%o
八进制数
%s
字符串
%x
十六进制数
2.8.2 example
$echo "65" | awk '{printf "%c\n",$0}'
(字符转换)
$awk 'BEGIN {printf "%c\n",65}'
(同上)
$awk '{printf "%-15s %s\n",$1,$3}' grade.txt
(格式化输出:左对齐)
$awk '{printf "%-15s %s\n",$1,$3}' grade.txt
(格式化输出:左对齐)
$awk 'BEGIN {printf "Namw \t\tS.Number}{print "%-15s %s\n",$1,$3}' grade.txt
2.8.3 向一行awk命令传值
在查看awk脚本前,先来查看怎样在awk命令行中传递变量。
在awk执行前将值传入awk变量,需要将变量放在命令行中,格式如下: awk 命令变量=输入文件值
下面的例子在命令行中设置变量AGE等于1 0,然后传入awk中:
$awk '{if($5
等效语句是:
$awk 'BEGIN {AGE=10} {if($5
$df -k |awk '($4~/^[0-9]/){if($4
awk也允许传入环境变量。下面的例子使用环境变量LOGNAME支持当前用户名:
$who | awk '{if($1==user) print $1" you are connect to "$2}' user=$LOGNAME
2.8.3 awk脚本文件
可以将awk脚本写入一个文件再执行它。
下面例子把一个awk命令用脚本文件来实现:
语句:awk‘(tot+=$6) END {print "club student total points:"tot}’ grade.txt
创建新文件student_tot.awk,给所有awk程序加入awk扩展名,文本如下:
!/bin/awk -f
#####zhu shi hang student_tot.awk
#print a header first
BEGIN{
print "student
date
member.no
age
point"
print "name
joined
gained
max"
print "==============================================="
}
(tot+=$6)
END{print "total points :" tot
print "average points:" tot/NR}
第一行是!/bin/awk -f。这很重要,没有它自包含脚本将不能执行。这一行告之脚本系统中awk的位置。
使用时执行 $student_tot.awk grade.txt
2.8.4 在awk脚本中使用FS变量
使用awk脚本时,记住设置FS变量是在BEGIN部分。如果不这样做,awk将会发生混淆,不知道域分隔符是什么。
下述脚本指定FS变量。脚本从/etc/passwd文件中抽取第1和第5域,通过":"分隔passwd文件域。第1域是帐号名,第5域是帐号所有者。
$pg passwd.awk
!/bin/awk -f
BEGIN{
FS=":"}
{print $1,"\t",$5}
执行
$passwd.awk /etc/passwd
2.8.5 向awk脚本传值
向awk脚本传值与向awk一行命令传值方式大体相同,格式为:
awk script_file var=value input_file
example 1:
$pg fieldcheck.awk
!/bin/awk -f
NF!=MAX{
print("line " NR "does not have " MAX " fields")}
执行 $fieldcheck.awk MAX=7 FS=":" /etc/passwd
example 2:
$pg name.awk
!/bin/awk -f
{if ($5 < AGE)
print $0}
执行 $name.awk AGE=10 grade.txt
同样可以使用前面提到的管道命令传值,下述awk脚本从du命令获得输入,并输出块和字节数。
example 3:
$pg duawk.awk
!/bin/awk -f
#to call: du |duawk.awk
BEGIN{
OFS="\t";
print "name" "\t\t","bytes","blocks\n"
print "==============================="}
{print $2,"\t\t",$1*512,$1}
执行 $du | duawk.awk
2.9
awk数组
前面的例子: $awk 'BEGIN {print split("123#456#4678",myarray,"#")}'
数组使用前,不必定义,也不必指定数组元素个数。经常使用循环来访问数组。下面是一种循环类型的基本结构:
For (element in array ) print array[element]
对于记录“123#456#678”,先使用split函数划分它,再使用循环打印各数组元素。操作脚本如下:
!/bin/awk -f
BEGIN{
record="123#456#789";
split(record,myarray,"#")}
END { for (i in myarray) {print myarray[i]}}
要运行脚本,使用/dev/null作为输入文件,执行 $ arraytest.awk /dev/null
可以通过split函数使用数组。也可以预先定义数组,并使用它与域进行比较测试。
完整的例子(数据包含了空手道数据库中学生级别及是否是成人或未成年人的信息,有两个域,分隔符为#)
$pg grade_student.txt
yellow#junior
orange#senior
yellow#junior
purple#junior
brown#junior
white#senior
orange#senior
red#junior
brown#senior
yellow#senior
red#junior
blue#senior
green#senior
purple#junior
white#junior
1)俱乐部中Yellow、Orange和Red级别的人各是多少。
2)俱乐部中有多少成年人和未成年人
脚本如下:
$pg belts.awk
!/bin/awk -f
#to call : belts.awk grade_student.txt
BEGIN{FS="#"
belt["yellow"]
belt["orange"]
belt["red"]
student["junior"]
student["senior"]
}
{for (colour in belt)
{if ($1==colour)
belt[colour]++ }}
{for (senior_or_junior in student)
{if ($2==senior_or_junior)
student[senior_or_junior]++ }}
END{for (colour in belt) print "The club has ",belt[colour],colour," belts"
for (senior_or_junior in student) print "The club has ",student[senior_or_junior],senior_or_junior\,
"students"}
BEGIN部分设置FS为符号#,即域分隔符,因为要查找Yellow、Orange和Red三个级别。然后在脚本中手工建立数组下标对
学生做同样的操作。注意,脚本到此只有下标或元素,并没有给数组名本身加任何注释。初始化完成后, BEGIN部分结束。
记住BEGIN部分并没有文件处理操作。
现在可以处理文件了。首先给数组命名为colour,使用循环语句测试域1级别列是否等于数组元素之一(Yellow、Orange
或Red),如果匹配,依照匹配元素将运行总数保存进数组。同样处理数组‘Senior_or_junior’,浏览域2时匹配操作满足,
运行总数存入junior或senior的匹配数组元素。
END部分打印浏览结果,对每一个数组使用循环语句并打印它。注意在打印语句末尾有一个\符号,用来通知awk(或相关
脚本)命令持续到下一行,当输入一个很长的命令,并且想分行输入时可使用这种方法。运行脚本前记住要加入可执行权限。