awk 学习笔记

awk分别代表其作者姓氏的第一个字母。

awk 基础

awk语法有两种形式:
awk [-F ERE] [-v assignment] 'program' [argument ...]
awk [-F ERE] -f progfile ... [-v assignment] ...[argument ...]

注意:第一种形式的 命令(program) 必须是用单引号引起。 awk -v a='a b c' 'BEGIN {print a; a=123} END{print a}'

标准的awk命令行参数主要由以下三个:

  • -F ERE:定义字段分隔符,该选项的值可以是一个字符串或扩展的正则表达式(ERE),如: -F: 表示以冒号分隔字段。
  • -f progfile:指定awk脚本,可以同时指定多个脚本,它们会按照在命令行中出现的顺序连接在一起。
  • -v assignment:定义awk变量,形式同awk中的变量赋值,即name=value,赋值发生在awk处理文本之前。

awk是一种编程语言,逐行扫描文件,寻找匹配特定模式的行,并在这些行上执行操作。
awk可以同时指定多个输入文件,如果输入文件的文件名为-,表示从标准输入读取内容。

Awk的输入被解析成多个记录(Record),默认情况下,记录的分隔符是 \n,因此可以认为一行就是一个记录,记录的分隔符可以通过内置变量RS更改。当记录匹配某个pattern时,才会执行后续的action命令。

而每个记录又进一步地被分隔成多个字段(Field),默认情况下字段的分隔符是空白符,例如空格、制表符等等,也可以通过-F ERE选项或者内置变量FS更改。在awk中,可以通过 $1,$2… 来访问对应位置的字段,同时 $0 存放整个记录,这一点有点类似shell下的命令行位置参数。

内置变量


变量名              描述
$0                    当前记录(存放整行的内容)
$1-$n               当前记录的第n个字段,字段间由FS分隔

FS                   输入的字段分隔符,默认为空格或Tab
NF                   当前记录中字段的个数,就是有多少列
RS                   输入的记录的分隔符,默认为换行符
NR                   已经读出的记录数,就是行号,从1开始,如果有多个文件,这个值也是不断累加。

FNR                 当前记录数,与NR不同的是,这个值是各个文件自己的行号
FILENAME         当前被处理的文件名

OFS                 输出时字段的分隔符,默认为空格
ORS                 输出时记录的分隔符,默认为换行符

ARGC                命令行参数的各个,即ARGV数组的长度
ARGV                存放命令行参数
CONVFMT         定义awk内部数值转换成字符串的格式,默认值为”%.6g”
OFMT               定义输出时数值转换成字符串的格式,默认值为”%.6g”
ENVIRON          存放系统环境变量的关联数组
RLENGTH          被match函数匹配的子串长度
RSTART           被match函数匹配的子串位于目标字符串的起始下标

模式和操作

awk脚本是由模式和操作组成的:pattern {action}
两者是可选的,如果没有模式,则action应用到全部记录,如果没有action,则输出匹配的全部记录。
默认情况下,每一个输入行都是一条记录,但用户可通过RS变量指定不同的分隔符进行分割。

模式

可以是一下任意一个:


模式                         说明
/正则表达式/            使用通配符的扩展集。如:  /abc/ 表示匹配含有abc的记录 。
关系表达式               用关系运算符进行操作,可以是字符串或数字的比较,如:$2 > %1  表示选择第二个字段比第1个字段大的行。
模式匹配表达式        用运算符~ 匹配 和~! 不匹配 模式。如:  $6 ~ /abc/ ,表示匹配第6个字段包含abc的记录。
模式, 模式                指定一个行的范围。该语法不能包括BEGIN和END模式。
BEGIN                      让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。如: awk 'BEGIN {FS=":"}'
END                         让用户在最后一条记录被读取之后发生的动作。如: echo -e "1\n2\n3\n12" | awk '{sum += $1} END {print sum}'

操作

由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内。主要有四部分:

  • 变量或数组赋值, a[i]++ 表示对数组a的第i个元素进行加1。
  • 输出命令 print 12 " = " 456 将输出 12 = 456.
  • 内置函数
  • 控制流命令 可以使用if else if else 语句

完整的awk命令可以是这样的:
awk ' BEGIN {处理文件前执行的命令} {对每一行进行处理的命令} END {处理完文件后执行的命令} ' files

运算符、表达式、变量

运算符


运算符                                    描述
= += -= *= /= %= ^= **=     赋值
?:                                           C条件表达式
||                                           逻辑或
&&                                         逻辑与
~ ~!                                       匹配正则表达式和不匹配正则表达式
< <= > >= != ==                   关系运算符
空格                                       连接
+ -                                         加,减
* / &                                      乘,除与求余
+ - !                                       一元加,减和逻辑非
^ ***                                    求幂
++ --                                      增加或减少,作为前缀或后缀
$                                            字段引用
in                                           数组成员

表达式(Expressions)

表达式可以由常量、变量、运算符和函数组成,常数和变量的值可以为字符串和数值。

变量

内建变量一般是大写的,参考前面“内置变量”。

用户定义变量可以是字符串或数字,不需要预先声明或初始化,未初始化的字符串变量的值为"",未初始化的数值变量的值为0

字段变量可以用$n来引用,n的取值范围为[0,NF],NF是当前行的记录总数。n可以为一个变量,例如$NF代码最后一个字段,而$(NF-1)表示倒数第二个字段。字段变量可被修改和赋值。

awk还可以环境变量,通过使用 -v 参数和 ENVIRON ,使用ENVIRON 的环境变量需要先 export。


export  y=12
echo ""  | awk -v var="abc"  'END{print var, ENVIRON["y"]}' 

正则元字符

通用的元字符集,与sed一样。


^     表示行的开头。
$     表示行的结尾。
.     句点表示任意单个字符
*     星号表示某个字符出现了0次或多次。
[]    字符集合。如[0-9]表示数字0-9中的任意一个,[a-zA-Z]表示字母中的任意一个。如果字符集以^开头表示非,如[^0-9]表示非数字。

gawk专用元字符集,不适合unix版本的awk。


\Y      匹配一个单词的开头或者末尾的空字符串。
\B      匹配单词内的空字符串。
\<      表示词首,如 \      表示词尾,如 abc\>表示以abc为尾的单词。
\w      匹配一个字母数字组成的单词。
\W     匹配一个非字母数字组成的单词。
\'       匹配字符串开头的一个空字符串。
\'       匹配字符串末尾的一个空字符串。

例子

使用运算符过滤记录

awk '$3 == 0 && $6 != "LISTEN" {printf "%-20s %-10s\n", $5, $6} ' netstat.txt

指定分隔符

下面两句是等价的:
awk 'BEGIN{ FS=":" } {print $1, $3, $6}' /etc/passwd
awk -F: '{print $1, $3, $6}' /etc/passwd

指定输出分隔符,必须在命令之后指定,之前指定会出错:
awk -F: '{print $1,$3,$6}' OFS="\t" /etc/passwd

使用变量和END模式

awk里的变量使用前不需要先定义,可以在 BEGIN 语句块里初始化。
echo -e "1\n2\n3\n12" | awk '{sum += $1} END {print sum}'

字符串匹配

awk '$6 ~ /FIN|TIME/ || NR == 1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
/FIN|TIME/ 表示匹配含有 FIN或 TIME。

字符串不匹配
awk '$6 !~ /FIN/ || NR == 1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt

拆分文件

awk 拆分文件可以使用重定向(>, >>)。有列值分区的感觉。

可以按某一列的值来拆分文件,以第N列为拆分列,其值为A、B、C三个值之一,那么所有第N列值为A的都行都被分拆到文件名为A的文件中,所有第N列值为B的都行都被分拆到文件名为B的文件中,所有第N列值为C的都行都被分拆到文件名为C的文件中。
awk 'NR!=1 {print > $6} ' netstat.txt

也可以选定输出列:
awk 'NR!=1 {print $3, $4, $5 > $6} ' netstat.txt

输出到指定文件
awk 'NR!=1 {if ($6 ~ /TIME|ESTABLISHED/) print > "1.txt"; else if ($6 ~ /LISTEN/) print > "2.txt"; else print > "3.txt" }' netstat.txt

awk 编程

重定向和管道

  • awk可以使用shell的重定向符进行输出重定向。如: awk 'NR!=1 {if ($6 ~ /TIME|ESTABLISHED/) print > "1.txt"; else if ($6 ~ /LISTEN/) print > "2.txt"; else print > "3.txt" }' netstat.txt

  • 输入重定向需用到getline函数。getline从标准输入、管道或者当前正在处理的文件之外的其他输入文件获得输入。 它负责从输入获得下一行的内容,并给NF, NR, FNR等内置变量赋值。如果得到一条记录,getline函数返回1,如果到达文件的末尾就返回0,出现错误就返回-1。
    awk 'BEGIN {"date" | getline d; print d}' , 得到输出 : Tue Apr 16 15:21:13 2013
    这里不需要指定输入文件,因为BEGIN块在打开输入文件前执行,所以可以忽略输入文件。

    awk 'BEGIN{printf "What is your name ?"; getline name < "/dev/tty"} $1 ~ name {print "Found " name " on line " NR "."} END{print " See you " name "."}' namefile
    这个例子从终端读入一行赋值给name,以name的值作为正则表达式来匹配。

    awk 'BEGIN{lc=0; while (getline < "/etc/passwd" > 0) lc++; print lc}' 从文件读取输入。

  • 可以在awk中打开一个管道,且同一时刻只能由一个管道存在。通过close()可关闭管道。如:awk '{print $1 | "sort" } END{close("sort")}' namefile,把print的输出作为sort的输入,最后在END块执行关闭管道的操作。

  • system 函数可以在awk中执行linxu的命令。如: awk 'BEGIN{system("clear")}'

  • fflush函数可以刷新输出缓冲区,如果没有参数,就刷新标准输出的缓冲区,如果以空字符串为参数,如fflush(""),则刷新所有文件和管道的输出缓冲区。

if else if else 语句

awk中的if与c的类似:


if (expression) {
     statements;
}

if (expression) {
     statements;
} else {
     statements;
}

if (expression) {
     statements;
} else if  (expression)  {
     statements;
} else {
     statements;
}

while、for循环

  • 这两个循环与C语言的还是很像的,但for循环支持在数组上进行for-each迭代。

while(condition) {
     statements;
}

for (init-statement; condition;  step-statment) {
     statements;
}

for (name/index  in  array) {
     statements;
}
  • breadkcontinue语句。break用于在满足条件的情况下跳出循环;continue用于在满足条件的情况下忽略后面的语句,直接返回循环的顶端。
    awk 'BEGIN{c=5; for(i=0; i < c; i++) if (i==2) {continue;} else if (i==3){break;}else{print i} }' namefile

  • next 语句从输入文件中读取一行,然后从头开始执行awk脚本。
    awk '{if ($1 ~ /bruce/){next} else {print $1 | "sort" }} END{close("sort")}' namefile

  • exit 语句用于结束awk程序,但不会略过END块。退出状态为0代表成功,非0表示出错。

数组

awk中的数组下标可以是数字和字母,称为关联数组。

  • 使用变量做数组下标: awk '{name[x++]=$1;} END{for (i=0;i<NR;i++) print i, name[i]}' namefile

  • 使用for-each循环


     for (item in arrayname){
          print arrayname[item]
     }
     
  • 用字符串作为下标。如:count[“test”]。

  • delete函数用于删除数组元素。如:$ awk ‘{line[x++]=$1} END{for(x in line) delete(line[x])}’ test。分配给数组line的是第一个域的值,所有记录处理完成后,for-each循环将删除每一个元素。

一个完整的例子如下所示:

<

pre>
echo "1 2 3" | awk '{
for (i=0;i<NF;i++)
a[i]=i;
}

END {
print 3 in a
for (i in a)
printf "%s: %s\n", i, a[i];
}'

还可以在if分支判断中使用in操作符: if (item in array)

函数

自定义函数

函数定义格式如下所示:function name(parameter list) { statements }

函数的参数列表用逗号分隔,参数默认是局部变量,无法在函数之外访问,而在函数中定义的变量为全局变量,可以在函数之外访问。
Awk脚本中的语句使用空行或者分号分隔,使用分号可以放在同一行,不过有时候会影响可读性,尤其是分支或循环结构中,很容易出错。
自定义函数的返回语句是可选的。

数学函数

awk中支持以下数学函数:
atan2(y,x):反正切函数;
cos(x):余弦函数;
sin(x):正弦函数;
exp(x):以自然对数e为底指数函数;
log(x):计算以e 为底的对数值;
sqrt(x):绝对值函数;
int(x):将数值转换成整数;
rand():返回0到1的一个随机数值,不包含1;
srand([expr]):设置随机种子,一般与rand函数配合使用,如果参数为空,默认使用当前时间为种子;

字符串函数

sub(ere, repl[, in])
将in中匹配ere的部分替换成repl,返回值是替换的次数。如果in参数省略,默认使用$0。替换的动作会直接修改变量的值。在repl参数中&是一个元字符,它表示匹配的内容

gsub(ere, repl[, in])
同sub()函数功能类似,只不过是gsub()是全局替换,即替换所有匹配的内容。

index(s, t)
返回字符串t在s中出现的位置,注意这里位置是从1开始计算的,如果没有找到则返回0。

length[([s])]
返回字符串的长度,如果参数s没有指定,则默认使用$0作为参数。

match(s, ere)
返回字符串s匹配ere的起始位置,如果不匹配则返回0。该函数会定义RSTART和RLENGTH两个内置变量。RSTART与返回值相同,RLENGTH记录匹配子串的长度,如果不匹配则为-1。

split(s, a[, fs])
将字符串按照分隔符fs,分隔成多个部分,并存到数组a中。注意,存放的位置是从第1个数组元素开始的。如果fs为空,则默认使用FS分隔。函数返回值分隔的个数。

sprintf(fmt, expr, expr, …)
类似printf,只不过不会将格式化后的内容输出到标准输出,而是当作返回值返回。

substr(s, m[, n])
返回从位置m开始的,长度为n的子串,其中位置从1开始计算,如果未指定n或者n值大于剩余的字符个数,则子串一直到字符串末尾为止。

tolower(s)
将字符串转换成小写字符。

toupper(s)
将字符串转换成大写字符。

I/O处理函数

getline

getline的用法相对比较复杂,它有几种不同的形式。不过它的主要作用就是从输入中每次获取一行输入。

  • expression | getline [var]
    这种形式将前面管道前命令输出的结果作为getline的输入,每次读取一行。如果后面跟有var,则将读取的内容保存到var变量中,否则会重新设置$0和NF。
    例如,我们将上面的statement.txt文件的内容显示作为getline的输入:
    [kodango@devops awk_temp]$ awk ‘BEGIN { while(“cat statement.txt” | getline var) print var}’
    statement
    delete
    exit
    next

如果不加var,则直接写到$0中,注意NF值也会被更新。

  • getline [var]
    第二种形式是直接使用getline,它会从处理的文件中读取输入。同样地,如果var没有,则会设置$0,并且这时候会更新NF, NR和FNR:
    [kodango@devops awk_temp]$ awk ‘{

    while (getline)
    print NF, NR, FNR, $0;
    }’ statement.txt
    1 2 2 delete
    1 3 3 exit
    1 4 4 next

  • getline [var] < expression
    第三种形式从expression中重定向输入,与第一种方法类似,这里就不加赘述了。

close

close函数可以用于关闭已经打开的文件或者管道,例如getline函数的第一种形式用到管道,我们可以用close函数把这个管道关闭,close函数的参数与管道的命令一致:
[kodango@devops awk_temp]$ awk ‘BEGIN {
while(“cat statement.txt” | getline) {
print $0;
close(“cat statement.txt”);
}}’
但是每次读了一行后,关闭管道,然后重新打开又重新读取第一行就死循环了。所以要慎用,一般情况下也很少会用到close函数。

system

这个函数很简单,就是用于执行外部命令,例如:
$ awk ‘BEGIN {system(“uname -r”);}’

参考资料


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据