awk命令

如果要格式化一个报文或从一个大的文本文件中抽取数据包,可以使用awk命令。它在文本浏览和数据的熟练使用上性能优异。

1. 调用awk

有三种方式调用awk,第一种是命令行方式:

$ awk [-F field-separator] 'commands' input-file(s)

这里,commands是真正的awk命令。[-F 域分隔符]是可选的,因为awk使用空格作为缺省的域分隔符,因此如果要浏览域间有空格的文本,不必指定这个选项,但如果浏览如password文件,此文件各域以冒号作为分隔符,则必须明确指明,awk -F: 'commands' input-file

第二种方法是将所有的awk命令插入一个文件,并使awk程序可以执行,然后用awk命令解释器作为脚本的首行,以便通过键入脚本名称来调用它。

第三种方式是将所有的awk命令插入一个单独文件,然后调用:awk -f awk-script-file input-file(s)-f指明在文件awk-script-file中的awk脚本。

2. awk脚本

如果设置了-F选项,则awk每次读一条记录或一行,并使用指定的分隔符分隔指定域;如果未设置-F选项,awk假定空格为域分隔符,并保持这个设置直到发现一新行。当新行出现时,awk命令知道已经读完整条记录,然后在下一个记录启动读命令,这个读进程将持续到文件尾或文件不再存在。

2.1 模式和动作

任何awk语句都由模式和动作组成。在一个awk脚本中可能有很多语句。模式部分决定动作语句何时触发时间。处理即对数据的操作。如果省略模式部分,动作将时刻保持执行状态。

模式可以是任何条件语句或复合语句或正则表达式。模式包括两个字段,BEGIN和END。使用BEGIN语句设置计数和打印头,BEGIN语句使用在任何文本浏览动作之前,之后文本浏览动作依据输入文件开始执行。END语句用来在awk完成文本浏览动作后打印输出文件总数和结尾状态标志。如果不特别指明模式,awk总是匹配或打印行数。

实际动作在大括号{}内指明,动作大多数用来打印,但是还有更长的代码诸如if和循环语句及循环退出结构。如果不指明采取动作,awk将打印出所有浏览出来的记录。

2.2 域和记录

awk执行时,其浏览域标记为$1, $2, ..., $n。这种方法称为域标识。使用$1,$3表示参照第1和第3域,这里用逗号作分隔。如果希望打印所有域,可使用$0awk浏览时,到达以新行,即假定到达包含域的记录末尾,然后执行新纪录的下一行的读动作,并重新设置域分隔。为了打印一个域或所有域,使用print命令,这是一个awk动作(动作语法用圆括号括起来)。

保存awk输出的话,可以使用重定向输出:

$ awk '{print $0}' input.txt >output.txt

这种方法不会将结果显示在屏幕上,并且会重写硬盘上同名文件。当然还可以使用tee命令,在输出到屏幕同时输出到文件:

$ awk '{print $0}' input.txt | tee output.txt

awk的输入

$ name.awk input.txt
# 重定向方法
$ name.awk < input.txt
# 管道方法
$ input.txt | name.awk

打印

# 单独打印记录
$ awk '{print $1,$4}' input.txt
# 打印报告头
$ awk 'BEGIN {print "Headname1 Headname2\n----------------"} {print $1"\t"$4}' input.txt
# 打印信息尾
$ awk 'BEGIN {print "Headname\n--------------"} {print $1} END {"end-of-report"}' ipt.txt

错误信息,遇到awk错误时,大概有几个常见的错误:

  • 确保整个awk命令用单引号括起来
  • 确保命令内所有引号成对出现
  • 确保用花括号括起来动作语句,用圆括号括起条件语句
  • 可能忘记使用花括号等等

正则表达式:正则表达式需要用斜线括起来,比如/Hello/可以查出单词Hello的情况。

元字符\ ^ $ . [] | () * + ?+匹配一个或多个字符,?匹配模式出现频率,这两个只适用于awk而不适用于grepsed

条件操作符<、 <=、 ==、 !=、 >、 >=、 ~、 !~~匹配正则表达式,!~不匹配正则表达式。

为使一域号匹配正则表达式,使用~后紧跟正则表达式,也可以用if语句:

$ awk '{if($4~/Hello/) print $0}' ipt.txt #如果域4包含Hello,打印匹配行记录
$ awk '$0 ~/Hello/' ipt.txt #如果记录包含模式Hello,打印,匹配记录找到时,不特别声明则缺省打印整条

如果{if($3~/32/) print $0}会匹配到诸如3212、32、3211等,想精确匹配到32则:

$ awk '$3=="48" {print $0}' ipt.txt

不匹配时:

$ awk '$0 !~/Hello/' ipt.txt #查找不包含模式Hello,缺省情况下将打印所有匹配记录,不必加动作部分

其它小于大于等:

$ awk '{if ($6 > $7) print $1}' ipt.txt
$ awk '{if ($5 <= $3) print $0}' ipt.txt

逻辑符号&& AND|| OR!

$ awk '{if ($1=="name1" && $2=="name2") print $0}' ipt.txt

2.3 内置变量

awk有许多内置变量用来设置环境信息,这些变量可以被改变。下表给出了最常使用的一些变量及含义:

命令 含义
ARGC 命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行-F选项,如设置,FS=","
NF 浏览记录的域个数
NR 已读的记录数
OFS 输出域分隔符,缺省为空格,如果设置为#可以OFS="#"
ORS 输出记录分隔符,缺省为新行\n
RS 控制记录分隔符,缺省为新行\n
FILENAME awk浏览的文件名

如使用NR将打印输入文件的记录个数:

$ awk 'END {print NR}' ipt.txt #可以在末尾输出已读的记录数

在下面例子,使用NF变量显示每一条读记录中有多少个域,NR显示已读记录数,并在END部分打印输入文件名:

$ awk '{print NF,NR,$0} END{print FILENAME}' ipt.txt

还可以使用NF显示目录或者文件名:

$ echo $PWD | awk -F/ '{print $NF}'

假设$PWD返回usr/local/etc,则将其作为awk输入,-F/指定域分隔符分隔出3个域,则NF值是3,$3则指定第3个域内容,即etc

$ echo "/usr/local/etc/hello.txt" | awk -F/ '{print $NF}' #输出 hello.txt

2.4 awk操作符

awk中使用操作符,基本表达式可以划分为数字型、字符串型、变量型、域及数组元素。

设置输入域到域变量名:在awk中设置有意义的域名是一种好的习惯,在进行域名匹配或关系操作时更容易理解。一般的变量名设置方式位name=$n,这里name为调用的域变量名,n为实际域号。多个设置之间用分号隔开。

$ awk '{name=$1;age=$4; if(age ~/42/) print name" is age:"age}' ipt.txt

域值比较操作:有两种方法测试一数值域是否小于另一数值域:1)在BEGIN中给变量名赋值。2)在关系操作中使用实际数值。

通常在BEGIN部分赋值是很有益的,可以在awk表达式进行改动时减少麻烦。使用关系操作必须用圆括号括起来。

$ awk '(if($6 < 27) print $0)' ipt.txt
$ awk 'BEGIN {BASELINE="27 "}{if($6 < BASELINE) print $0}' ipt.txt #两者作用相同

其中用引号将27括起来是可选的,效果一样。

修改数值域取值:在awk中修改任何域时,重要的一点要记住实际输入文件是不可修改的,修改的只是保存在缓存里的awk副本。awk会在变量NRNF中反映出修改痕迹。

为修改数值域,简单的给域标识重新赋值,如$1=$1+5,会将$1的值加5,当然在实施修改前首先要匹配域名。

$ awk '{if($1=="Hello") $6=$6-1; print $1,%6}' ipt.txt

修改文本域:修改文本域即对其重新赋值。需要做的是赋给一个新的字符串。记住字符串需要使用双引号,并用圆括号括起来整个语法。

$ awk '{if($1=="hello") ($1="world"); print $1}' ipt.txt

只显示修改记录:如果一个文件记录数很多,打印全部只为了查看修改部分显然不合理。在模式后面使用花括号将只打印修改部分:

$ awk '{if($1=="hello"){$1="world";print $1}}' ipt.txt

创建新的输出域:在awk中处理数据时,基于各域进行计算时创建新域是一种好的习惯。创建新域要通过其它域赋予新域标识符。如创建一个基于其它域的加法新域{$4=$2+$3},这里假定记录包含3个域,则域4位新建域,保存域2和域3相加结果。

$ awk '{if($6<$7) {$8=$7-$6; print $1,$8}}' ipt.txt #$8是新域

或者可以直接使用更具有意义的变量名:

$ awk 'if($6<$7){diff=$7-$6; print $1,diff}' ipt.txt

增加列值:为增加列数或进行运行结果统计,使用符号+=。例如将$1加入变量totaltotal+=$1。列值增加很有用,比如许多文件都要求统计总数:

$ awk '(total+=$6); END{print "total: " total}' ipt.txt

如果不想打印所有记录,只打印累加结果:

$ awk '{(total+$6)}; END{print "total: "total}' ipt.txt

文件长度相加:在目录中查看文件时,如果想快速查看所有文件的长度及其总和,但要排除子目录,使用ls -l命令,然后管道输出到awkawk首先使用正则剔除首字母d的记录,然后将文件长度列相加,输出到每一文件长度及在END部分输出所有文件的长度。

$ ls -l | awk '/^[^d]/ {print $9"\t"$5} {tol+=$5} END{print "total KB: "tol}'

2.5 内置的字符串函数

字符串函数 含义
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,第二个函数允许指定目标,然而未指定目标,缺省为$0

index(s,t)函数返回目标字符串s中查找字符串t的首位置。split使用域分隔符fs将字符串s划分为指定序列asub(r,s)将用s替代$0中最左边最长的子串,该子串被r匹配。

# 1.gsub 使用正则表达式,/目标模式/
$ awk 'gsub(/48311/,48322) {print $0}' ipt.txt
# 2.index 必须用双引号将字符串括起来
$ awk 'BEGIN{print index("Lulu", "lu")}' ipt.txt
# 3.length
$ awk '$1=="Hello" {print length($1)" "$1}' ipt.txt
# 4.match 测试字符串是否包含查找字符的一部分,可对查找部分使用正则,返回值为成功出现的字符排列数
$ awk 'BEGIN {print match("ANCD",/d/)}' # 无,返回0
# 5.split
$ awk 'BEGIN {print split("123#456#789", myarray, "#")}' #返回3
# 6.sub
$ awk '$1=="Hello" sub(/26/, "29", $0)' ipt.txt
# 7.substr
$ awk '$1=="Hello" {print substr($1,1,5)}' ipt.txt

2.6 字符串屏蔽序列

使用字符串或者正则表达式时,有时需要在输出中加入一新行或查询一元字符。

打印一新行时(\n),给出其屏蔽序列,以不失其特殊含义,用法在字符串前加入反斜线。

如果使用正则表达式,查询花括号{},在字符前加反斜线,如/\{/,将在awk中失去其特殊含义。

$ awk 'BEGIN {print "\n\May\tDay\n\nMay    \104\141\171"}'

\ddd表示八进制ASCII码。

2.7 awk输出函数printf

awk提供函数printf,拥有集中不同的格式化输出功能。例如按列输出、左对齐或右对齐等。

每一种printf函数(格式控制字符)都以一个%符号开始,以一个决定转换的字符结束。转换包含三种修饰符。

printf修饰符:

修饰符 含义
- 左对齐
Width 域的步长,用0表示0步长
.prec 最大字符串长度,或小数点右边的位数

printf格式:

格式 含义
%c ASCII字符
%d 整数
%e 浮点数,科学计数法
%f 浮点数
%g awk决定使用ef
%o 八进制数
%s 字符串
%x 十六进制数
$ echo "65" | awk '{printf "%c\n", $0}' # 输出 A
$ awk 'BEGIN{printf "%c\n", 65}' # 输出 A
$ awk '{printf "%-15s %s\n", $1, $3}' ipt.txt # -表示左对齐 15表示字符长度

向一行awk命令传值,下面的例子在命令行中设置变量AGE等于10,然后传入awk中:

$ awk '{if($5 < AGE) print $0}' AGE=10 ipt.txt

2.8 脚本文件

可以将awk脚本写入一个文件再执行,这样可以保存,不必每次使用都重新输入。

如创建一个student.awk文件:

!/bin/awk -f
# comment line
# to call: student.awk ipt.txt

#print header first
BEGIN{
print "Student    Date    Member    Grade        Age        points"
print "Name            Joined"
print "===================================="
}
# add the points
(tol+=$6)
# print the total and average point
END{
print "Club student total points: " tol
print "Average points: " tol/NR
}

第一行!/bin/awk -f非常重要,没有它脚本将不能执行。

在脚本文件中设置FS分隔符时,需要在BEGIN部分,不然awk将会发生混淆,不知道域分隔符是什么。

在脚本传值类似:

$ awk script_file var=value input_file

2.9 awk数组

前面说到split返回数组myarray下标数,实际上myarra为:myarray[1]、myarray[2]、myarray[3]

数组使用前不必定义,也不必指定数组元素个数,经常使用循环来访问数组:

for (element in array) print array[element]

results matching ""

    No results matching ""