文本处理三剑客之awk(原创)
AWK是一种优良的文本处理工具,Linux及Unix环境中现有的功能最强大的数据处理引擎之一。这种编程及数据操作语言(其名称得自于它的创始人阿尔佛雷德·艾侯(Alfred Aho)、彼得·溫伯格(Peter Jay Weinberger)和布萊恩·柯林漢(Brian Wilson Kernighan)姓氏的首个字母)的最大功能取决于一个人所拥有的知识。AWK提供了极其强大的功能:可以进行正则表达式的匹配,样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。它具备了一个完整的语言所应具有的几乎所有精美特性。实际上AWK的确拥有自己的语言:AWK程序设计语言,三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。gawk是AWK的GNU版本。最简单地说,AWK是一种用于处理文本的编程语言工具。AWK在很多方面类似于Unix shell编程语言,尽管AWK具有完全属于其本身的语法。它的设计思想来源于SNOBOL4、sed、Marc Rochkind设计的有效性语言、语言工具yacc和lex,当然还从C语言中获取了一些优秀的思想。在最初创造AWK时,其目的是用于文本处理,并且这种语言的基础是,只要在输入数据中有模式匹配,就执行一系列指令。该实用工具扫描文件中的每一行,查找与命令行中所给定内容相匹配的模式。如果发现匹配内容,则进行下一个编程步骤。如果找不到匹配内容,则继续处理下一行。
在我们的Linux中,awk其实是gawk的一个软连接,所以我们所使用的都是GNU AWK:
awk的运行方式:
(1) awk命令行
# awk
(2) awk程序文件
# awk -f /path/from/awk_script
(3) awk脚本
#!/bin/awk –f
Awk在处理文本时会将每一行读取到自己的内存空间当中,并将每一行文本根据我们制定的分隔符等分成不同的区域,类似脚本的内置位置变量。在处理完之后,输出时awk依然是按照一行来输出的,即便是输出的内容不是原来的一整行,输出的分割符默认为空格,也可以自定义。
基本用法:
gawk [OPTIONS] 'program' FILE1 FILE2 ...
program: PATTERN{ACTION STATEMENT}
由语句组成,各语句间使用分号分隔;
ACTION: print, printf
选项:
-F[]: 指明输入字段分隔符,可以是多个,例:-F [:,]即用:当分隔符又用,当分隔符;
-v VAR_NAME=VALUE: 变量赋值;
-f /PATH/FROM/AWK_SCRIPT:
1、awk的输出命令之一:print
用法:print item1, item2, ...
item:
字符串:用引号引用;
print "hello", "world"
变量:显示变量的值;
print name
引用变量:直接使用变量名
数值:无须加引号
要点:
(1) 各item之间需要使用逗号分隔;而输出时的分隔符为默认为空白字符;
(2) 输出的各item可以为字符串或数值、当前记录的字段($#)、变量或awk的表达式;数值会被隐式转换为字符串进行输出;
(3) print后面的item省略时,相当于运行“print $0”,用于输出整行;
(4) 输出空白字符:print " "
2、变量
内建变量,自定义变量
2.1 内置变量
AWK的内建变量包括域变量,例如$1, $2, $3,以及$0。这些变量给出了记录中域的内容。 内建变量也包括一些其他变量:
NR:已输入记录的条数。
FNR:行数,各文件单独计数。
NF:当前记录中域的个数。记录中最后一个域可以以$NF的方式引用。print NF 每行有几个字段,print $NF 显示每一行的最后一个字段
FILENAME:当前输入文件的文件名。
FS:“域分隔符”,用于将输入记录分割成域。其默认值为“空白字符”,即空格和制表符。FS可以替换为其它字符,从而改变域分隔符。-v FS="[ ,:.]"
RS:当前的“记录分隔符”。默认状态下,输入的每行都被作为一个记录,因此默认记录分隔符是换行符。
OFS:“输出域分隔符”,即分隔print命令的参数的符号。其默认值为空格。
ORS:“输出记录分隔符”,即每个print命令之间的符号。其默认值为换行符。
OFMT:“输出数字格式”(Format for numeric output),其默认值为"%.6g"。
ARGC:awk命令行中的参数的个数。
ARGV:数组,保存了命令行参数本身。
2.2 自定义变量
(1) -v VAR_NAME=VALUE
变量名区分字符大小写;
(2) 在program中自定义变量
# awk 'BEGIN{FS=":";f1=3}{print $f1}' /etc/passwd
f1赋值一次一直使用
每读取一行f1赋一次值
3、awk的输出命令之二:printf
语法:printf FORMAT(格式),item1,item2,...
要点:
(1) 必须提供FORMAT;
(2) 与print语句不同,printf不会自动换行,需要显式指定换行符:\n
(3) FORMAT中需要分别为后面的每个item指定一个格式符,否则item则无法显示;
格式符:都以%开头,后跟单个字符;
%c: 显示字符的ASCII码;
%d, %i:显示为十进制整数;
%e, %E: 科学计数法显示数值;
%f:显示为浮点数;
%g, %G:以科学计数法或浮点数格式显示数值;
%s: 显示为字符串;
%u:显示无符号整数;
%%: 显示%符号自身;
修饰符:
#[.#]:
左边的#:用于指定显示宽度;
右边的#: 显示精度;
+:显示数值符号
-:左对齐
4、操作符
算术操作符:
x+y, x-y, x*y, x/y, x^y, x%y
-x: 负值
+x: 转换为数值
字符操作符:字符串连接
赋值操作符:
=, +=, -=, *=, /=, %=, ^=
++, --
比较操作符:
>, >=, <, <=, ==, !=
模式匹配操作符:
~:是否能由右侧指定的模式所匹配;
!~:是否不能由右侧指定模式所匹配;
逻辑操作符:
&&:与运算
||: 或运算
条件表达式:
selector?if-true-expression:if-false-expression
# awk -F: '{$3>=500?usertype="Common User":usertype="Sysadmin or Sysuser";printf "%15s:%-s\n",$1,usertype}' /etc/passwd
函数调用:
function_name(argu1, argu2, ...)
5、PATTERN模式
(1) empty:空模式,匹配所有行;
(2) /Regular Expression/(正则表达式):仅将ACTION应用于能够被Regular Expression所匹配到的行;
例如:awk -F: '/^[ab]/{print $1,$3}' /etc/passwd
(3) relational expression:关系表达式,即结果为“真”、“假”的表达式, 或者其结果能类同于“真”或“假”的表达;一般来说,其结果为非0数值或非空字符串即可类同为“真”,否则,则类同为“假”;
例如:awk -F: '$3>=500{print $1,$3}' /etc/passwd
例如:awk -F: '$1~/root/{print $1,$3}' /etc/passwd
(4) line ranges:行范围,类似sed或vim中的地址定界方式
startline,endline
此方式我没有试验成功,待定
(5) BEGIN/END:两个特殊模式
BEGIN:在文件格式化操作开始之前事先执行的一次操作;通常用于输出表头或做出一个预处理操作;
END:在文件格式操作完成之后,命令退出之前执行的一次操作;通常用于输出表尾或做出清理操作;
6、常用ACTION
(1) EXPRESSIONS:例如变量赋值
(2) Control Statements:控制语句,如if, while等;
(3) Compound Statements:复合语句
(4) input statements:输入语句
(5) output statements:输出语句
7、控制语句
if (condition) { statements } [else { statement }]
while (condition) { statements }
do statement while (condition)
for(expr1;expr2;expr3) { statements }
for(var in array) { statements }
switch (expression) {case VALUE or /REGEXP/: statement; ...; default: statementN}
break
continue
delete array[index]
delete array
exit [ expression ]
{ statements }
7.1 if-else
语法:if (condition) statement [ else statement ]
if (condition) {statements} [else {statements}]
~]# awk -F: '{if ($3>=500) print $1,$3}' /etc/passwd
~]# awk '{if (NF>=6) print NF,$0}' /etc/rc.d/rc.sysinit
~]# awk -F: '{if ($3>=500) {print $1,"is a common user"} else {print $1,"is a sysadmin or sysuser"}}' /etc/passwd
使用场景:对awk取得的整行或行中的字段做条件判断;
7.2 while循环
语法:while (condition) statement
while (condition) {statements}
条件为“真”时循环,为“假”时退出循环;
使用场景:通常用于在当前行的各字段间进行循环;
~]# awk '{i=1;while(i<=NF){if (length($i)>=6) {print $i};i++}}' /etc/issue
7.3 do-while循环
语法:do statement while (condition)
do {statements} while (condtion)
意义:至少执行一次循环体
7.4 for循环
语法:for (expr1;expr2;expr3) statement
for (expr1;expr2;expr3) {statements}
for (variable assignment;codition;iteration process) {for-body}
# awk '{for(i=1;i<=NF;i++){if(length($i)>=6) print $i}}' /etc/issue
第二种用法:用于遍历数组中的元素
for (var_name in array) {for-body}
7.5 swtich
语法:switch (expression) {case VALUE or /REGEXP/: statement; ...; default: statementN}
7.6 break and continue
break [n]:退出当前循环,n是一个数字,用于指定退出几层循环;
continue:提前结束本轮循环而进入下一轮;
7.7 next
提前结束对本行文本的处理,而提前进入下一行的处理操作;
~]# awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd
8、数组
关联数组:array[index-expression]
index-expression:
可以使用任意字符;
如果某数组元素事先不存在,则在引用时,awk会自动创建此元素并将其值初始化为空串;
因此,若要判断 数组中的某元素是否存在,要使用“index in array”的方式进行判断;
要遍历数组中的元素,则要使用for (var_name in array)的方式进行;此时,var_name会遍历array的每个索引,所以,要显示数组元素的值,要使用array[var_name]
weekdays
weekdays[mon]="Monday"
weekdays[tue]="Tuesday"
...
for (i in weekdays):此时,i变量会遍历weekdays数组的每个索引,即mon, tue,而非元素的值“Monday”或"Tuesday"等;
要获取元素的值:weekdays[i]
9、函数
内建函数和用户自定义函数
9.1 内建函数
数值处理:
rand():返回0至1之间的一个随机数;
字符串处理:
length([s]):返回指定的字符串的长度;
sub(r,s[,t]):基于r所表示的模式来匹配字符串t中的内容,将其第一次被匹配到的内容替换为s所表示的字符串;
gsub(r,s[,t]):基于r所表示的模式来匹配字符串t中的内容,将其所有被匹配到的内容均替换为s所表示的字符串;
split(s,a[,r]):以r为分隔符去切割字符串s,并将切割后的结果保存至a表示的数组中;
注意:awk的数组下标从1开始编号
~]# awk '{split($0,userinfo,":");print userinfo[1]}' /etc/passwd
substr(s,i[,n]):从s所表示的字符串中取子串,取法:从i表示的位置开始,取n个字符;
时间类函数:
systime():取当前系统时间,结果形式为时间戳;
9.2 用户自定义函数
function f_name(p,q) {
...
}