七种武器之awk

[TOC]

awk是Bell实验室出品,它的作者Alfred Aho、Brian Kernighan和Peter Weinberger。同时,awk源自sed和grep,它同样也是一种模式匹配工具,每个指令均包含两个部分:模式和过程。模式一般是由斜杠(/)分隔的正则表达式,用于匹配当前行的字符串;过程是由大括号({})引起的awk语句和函数,用于指定一个或多个将被执行的动作。

命令格式

awk有两种语法形式:直接在终端inline脚本,或者调用一个awk脚本。

awk [options] 'script' var=value file(s)
awk [options] -f scriptfile var=value file(s)

除了直接在脚本后设置自定义变量,也可以利用-v设置自定义变量,传入外部参数,可以定义多个变量,例如:

awk -v t="test" 'BEGIN{print t}'

模式和过程

awk脚本由模式和过程组成,但也无需显示指定。如果没有模式,则过程应用到全部记录;如果没有过程,则输出匹配的全部记录。

模式可以是多种类型。

  • 正则表达式:使用斜杠标识 /正则表达式/
  • 关系表达式:可以使用各种关系运算符比较
  • 模式匹配:使用运算符匹配正则表达式,~ 表示匹配,~! 表示不匹配
  • 模式范围:使用“模式1,模式2”处理“模式1”第一次出现到“模式2”第一次出现之间的所有行
  • BEGIN:在第一条输入记录被处理之前的过程,通常用于设置全局变量
  • END:在最后一条输入记录被读取之后的过程

过程可以由一条或多条命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内。

  • 变量或数组赋值
  • 输出命令
  • 内置函数
  • 控制流命令

双斜杠//可以指定任意正则表达式,对匹配的行进行处理。

例如,输出匹配到foo的行,类似grep:

awk '/foo/{print}' file.txt

例如,统计文件中的空行:

awk '/^$/{ n++ }END{ print x }' file.txt

同样,也可以利用模式匹配操作符~在record内部使用模式匹配。

例如,只打印匹配到foo开头的列:

awk '{for(i=1;i<=NF;i++){
    if($i ~ /foo.*/) print
}}' file.txt

例如,输出第15行到20行的内容,类似sed:

awk 'NR==15,NR==30{print}' file.txt

内置变量

每个以换行符结束的行称作记录(record),使用$0引用整条记录,默认分隔符为换行符。记录中的每个单元称作(field),使用$1$2、…、$NF引用各个域,默认分隔符为空格或TAB。

  • FS 域分隔符,只能是单个字符,等同于命令行参数-F
  • OFS 输出的域分隔符
  • RS 记录分隔符,可以是多个字符,比如 BEGIN{ RS="\n\n" } 就是以两个\n来分隔行
  • ORS 输出的记录分隔符
  • NR 记录总数,默认等于已经处理的行数
  • NF 当前记录内的域总数,即每行的总列数
  • FNR 类似于NR,用于多个输入文件时,各个文件的记录数
  • FILENAME 当前处理的文件名(完整路径)
  • IGNORECASE 如果为真,则忽略大小写匹配
  • ENVIRON 系统环境变量的关联数组

所有的内置变量都可以在BEGIN块段设置,例如:

awk 'BEGIN{ RS="\n\n" }{print}'

注意FILENAME是当前处理的完整文件路径,即传给awk的参数。一个常用场景是打印前一个文件里不存在的行,例如:

awk '{
    if(FILENAME=="exclude.txt"){
        map[$0]=1
    }else{
        if(!($0 in map)){
            print
        }
    }
}' exclude.txt full.txt

IO重定向

重定向输出直接使用>符即可,重定向输入需要借助getline函数。

例如,按照第二列分割文件:

awk '{print $0 >"split_"$2 }' file.txt

例如,读入ls命令的输入:

awk 'BEGIN{ while("ls" | getline) print'

例如,在awk中打开一个有名管道:

awk '{print $1, $2|"foo"}END{close("foo")}' file.txt

关联数组

例如,输入n行电话号码,输出这些号码的出现次数:

awk '{map[$1]++}END{for(k in map){print k"\t"map[k]}}' file.txt

内部函数

函数sub匹配记录中最大、最靠左边的子字符串的正则表达式,并用替换字符串替换这些字符串。如果没有指定目标字符串,则默认使用整个记录。

sub ( regular expression, substitution string, [ target string ] )

例如,只在第一个域中替换:

awk '{sub(/test/,"mytest",$1); print}' file.txt

如果需要全文匹配,需要使用gsub函数。


函数index返回子字符串第一次被匹配的位置,偏移量从位置1开始。

index ( string, substring )

例如,下例输出3:

awk '{print index("test", "mytest")}' file.txt

函数length返回数组或者字符串的长度。

length ( [ string ] )

例如,打印长度超过100B的行:

awk '{if(length($0)>100)print' file.txt

函数substr用于截取部分字符串,注意index从1开始。

substr ( string, starting position, [ length of string ] )

例如,打印每行的前5个字符:

awk '{print substr($0, 1, 5)}' file.txt

函数match返回字符串中正则表达式位置的索引,如果没有匹配的,返回0。

match ( string, regular expression )

例如,打印连续以小写字符结尾字符串的开始位置:

awk '{start=match("this is a test",/[a-z]+$/); print start, RSTART, RLENGTH}'

函数split用于分割字符串到数组,如果没有提供分隔符,默认使用FS的值。

split ( string, array, [ field separator ] )

例如,每行单词由冒号分隔:

awk '{split($1, arr, ":"); for(i=1;i<length(arr);i++) print arr[i];}' file.txt

函数printf类似C语言同名函数,可以进行格式化输出。

printf ( format, ... )

例如,按4位数打印行号:

awk '{printf "%4d %s", NR, $0}' file.txt

函数rand用于产生随机数。

例如:

for i in $(seq 1 5) ; do
    echo | awk 'BEGIN{srand();}{print rand()}'
done

自定义函数,格式:

function name ( parameter, ... )
{
    statements
    return expression
}