AWK的简介和教程
AWK是一种强大的文本处理工具,它可以用简单的编程语言来实现复杂的文本操作。AWK是由Aho、Weinberger和Kernighan三位大牛在1977年开发的,因此也叫做AWK语言。AWK的名字来源于它们的姓氏首字母。
AWK的基本思想是将输入文件分割成多个记录和字段,然后对每个记录执行一系列的模式和动作。模式是一种条件表达式,用来匹配记录或字段。动作是一段AWK代码,用来处理匹配的记录或字段。
AWK有很多实现版本,其中最常用的是GNU AWK(gawk),它是GNU项目的一部分,拥有很多扩展功能。本文将以gawk为例来介绍AWK的基本用法和高级技巧。
AWK的基本语法
AWK的基本语法如下:
awk [options] 'program' file1 file2 ...
其中,options是一些可选的参数,如-F
指定字段分隔符,-v
指定变量赋值,-f
指定程序文件等。program是一段AWK代码,通常由一对单引号包围。file1 file2 …是要处理的输入文件,可以有多个。
AWK程序由一系列的模式-动作对组成,每个模式-动作对之间用换行符或分号分隔。模式-动作对的格式如下:
pattern { action }
其中,pattern是一个条件表达式,可以是正则表达式、算术表达式、字符串表达式、逻辑表达式等。action是一段AWK代码,可以包含变量、运算符、函数、控制流等。如果省略pattern,则默认匹配所有记录。如果省略action,则默认打印匹配的记录。
例如,下面这个AWK程序打印所有包含foo的记录:
awk '/foo/' file
下面这个AWK程序打印所有第一个字段大于10的记录:
awk '$1 > 10' file
下面这个AWK程序打印所有记录的第二个字段,并在前面加上行号:
awk '{ print NR, $2 }' file
AWK的内置变量
AWK有很多内置变量,用来存储输入文件、记录和字段的相关信息。下面列举一些常用的内置变量:
FS
:输入字段分隔符,默认为空格或制表符。OFS
:输出字段分隔符,默认为空格。RS
:输入记录分隔符,默认为换行符。ORS
:输出记录分隔符,默认为换行符。NF
:当前记录的字段数。NR
:当前记录的编号。FNR
:当前文件中的当前记录编号。FILENAME
:当前输入文件名。ARGC
:命令行参数个数。ARGV
:命令行参数数组。ENVIRON
:环境变量数组。
例如,下面这个AWK程序将输入文件中以逗号分隔的字段转换成以冒号分隔的字段:
awk 'BEGIN { FS = ","; OFS = ":" } { print $1, $2, $3 }' file
下面这个AWK程序将输入文件中每行末尾添加一个感叹号:
awk 'BEGIN { ORS = "!\n" } { print }' file
下面这个AWK程序打印每个文件中最后一行:
awk 'FNR == 1 && NR != 1 { print prev } { prev = $0 } END { print prev }' file1 file2 ...
AWK的数组
AWK支持一种特殊类型的数组,叫做关联数组(associative array)。关联数组可以使用任意类型的值作为索引(下标),而不仅仅是数字。关联数组在定义时不需要指定大小,在使用时可以动态增长。
例如,下面这个AWK程序统计输入文件中每个单词出现的次数,并按照字母顺序输出结果:
awk '{ for (i = 1; i <= NF; i++) count[$i]++ }
END { for (word in count) print word, count[word] }' file | sort
注意,关联数组遍历时并不保证按照索引顺序输出,所以需要使用sort命令进行排序。
AWK的函数
AWK提供了很多内置函数,用来执行各种操作,如数学运算、字符串处理、时间格式化等。除了内置函数外,AWK还允许用户自定义函数。
数学函数
AWK提供了很多数学函数,如sin、cos、exp、log、sqrt等。这些函数接受一个数值参数,并返回一个数值结果。
例如,下面这个AWK程序计算输入文件中每个数字的平方根,并保留两位小数:
awk '{ printf "%.2f\n", sqrt($1) }' file
字符串函数
AWK提供了很多字符串函数,如length、index、substr、split等。这些函数接受一个或多个字符串参数,并返回一个字符串结果或一个数组。
例如,下面这个AWK程序计算输入文件中每行字符数,并输出最长行和最短行:
awk 'length > max { max = length; longest = $0 }
length < min || min == 0 { min = length; shortest = $0 }
END { print "Longest:", longest; print "Shortest:", shortest }' file
时间函数
GNU AWK提供了一些时间函数,如systime、strftime等。这些函数可以获取系统时间或格式化时间字符串。
例如,下面这个AWK程序在每行前面添加当前时间戳:
awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0 }' file
用户自定义函数
除了使用内置函数外,用户还可以自定义自己的函数。自定义函数需要在调用之前定义,并遵循以下语法:
function name(parameter-list) {
statements
}
其中,name是函数名,parameter-list是参数列表(可以为空),statements是函数体。
例如,下面这个AWK程序定义了一个求两数最大值的函数,并在主程序中调用它:
function max(a, b) {
if (a > b) return a
else return b
}
{ print max($1, $2) }
AWK高级技巧
除了上述基本用法外,AWK还有很多高级技巧,可以让你更灵活地处理文本数据。下面介绍几个常见的高级技巧。
正则表达式
正则表达式是一种描述文本模式的语言,在AWK中可以广泛使用。正则表达式可以作为模式匹配记录或字段,也可以作为运算符匹配或替换字符串。
例如,下面这个AWK程序打印所有以大写字母开头并且包含数字的单词:
awk 'BEGIN { FS = "[ \t.,]+" }
{ for (i = 1; i <= NF; i++) if ($i ~ /^[A-Z].*[0-9]/) print $i }' file
上面的代码中,~
是正则表达式匹配运算符,/
是正则表达式的定界符,^
是行首锚点,.
是任意字符,*
是重复零次或多次,[0-9]
是数字字符类。
正则表达式还可以用于替换字符串,使用sub
或gsub
函数。sub
函数只替换第一个匹配的子串,而gsub
函数替换所有匹配的子串。它们的语法如下:
sub(regexp, replacement, target)
gsub(regexp, replacement, target)
其中,regexp是正则表达式,replacement是替换字符串,target是目标字符串。如果省略target,则默认为$0。
例如,下面这个AWK程序将输入文件中所有的foo替换成bar:
awk '{ gsub(/foo/, "bar"); print }' file
输入输出重定向
AWK可以使用输入输出重定向符来读写文件。输入重定向符是<
,输出重定向符是>
或>>
。其中,>
表示覆盖写入文件,而>>
表示追加写入文件。
例如,下面这个AWK程序将输入文件中每行的第一个字段写入到output.txt文件中:
awk '{ print $1 > "output.txt" }' file
注意,在AWK中使用输出重定向时,需要将文件名用双引号包围,并且不需要在print语句后面加分号。
输入重定向可以用于读取其他文件的内容,并与当前记录进行比较或处理。
例如,下面这个AWK程序将input.txt文件中每行的第一个字段与dict.txt文件中的每个单词进行比较,如果相同,则打印该行:
awk '{ while ((getline word < "dict.txt") > 0)
if ($1 == word) { print; break }
close("dict.txt")
}' input.txt
上面的代码中,getline函数用于从文件中读取一行,并返回读取状态。close函数用于关闭打开的文件。
BEGIN和END块
BEGIN和END块是两种特殊的模式-动作对,它们不依赖于输入文件的内容。BEGIN块在读取第一行之前执行,通常用于初始化变量、设置字段分隔符、打印标题等。END块在读取最后一行之后执行,通常用于打印汇总信息、清理资源等。
例如,下面这个AWK程序在BEGIN块中设置字段分隔符为逗号,在主程序中计算每行第二个字段的总和,在END块中打印总和:
awk 'BEGIN { FS = ","; sum = 0 }
{ sum += $2 }
END { print "Sum:", sum }' file
多文件处理
AWK可以同时处理多个输入文件,并使用FILENAME变量来区分不同的文件名。当切换到新的输入文件时,FNR变量会重新从1开始计数,而NR变量会继续累加。
例如,下面这个AWK程序打印每个输入文件中最长的一行:
awk 'FNR == 1 { if (NR != 1) print max, file; max = 0; file = FILENAME }
length > max { max = length; line = $0 }
END { print max, file }' file1 file2 ...
上面的代码中,在处理每个新文件时,先判断是否需要打印上一个文件的结果,然后重置最大长度变量和当前文件名变量。在处理每一行时,如果长度大于最大长度,则更新最大长度变量和当前行变量。在处理完所有文件后,打印最后一个文件的结果。
le; max = 0; file = FILENAME }
length > max { max = length; line = $0 }
END { print max, file }’ file1 file2 …
上面的代码中,在处理每个新文件时,先判断是否需要打印上一个文件的结果,然后重置最大长度变量和当前文件名变量。在处理每一行时,如果长度大于最大长度,则更新最大长度变量和当前行变量。在处理完所有文件后,打印最后一个文件的结果。