awk对输入的行进行分割操作,然后对分割后的列集合进行操作。列集合中的元素下标从1开始,$1代表第一列,以此类推。在默认情况下分割符采用的是空格,当然你可以根据不同的业务需求修改分隔符。

参数

awk [ -F fs ] [ -v var=value ] [ ‘prog’ | -f progfile ] [ file … ]

-F fs

用于指定分隔符,默认情况下为空格

echo "abc:def:ghi" | awk '{print $1}'

输出

abc:def:ghi

由于上面的文本中没有空格,因此采用空格分割的时候,该文本被当做了一列然后输出。如果将上面的命令修改为

echo "abc:def:ghi" | awk -F ":" '{print $1}'

输出将变为 abc

-v var=value

-v参数用于指定变量,指定的变量可以在后续的处理中用到。-v参数可以出现多次。

echo "abc:def:ghi" | awk -v test=test1 -F ":" '{print $1" "test}'

输出

abc test1

在-v参数中指定了变量test,其值为test1,所以在后面打印test的时候,输出了test1。

echo "abc:def:ghi" | awk -v test=test1 -v test1=test2 -F ":" '{print $1" "test" "test1}'

输出

abc test1 test2

上面的例子中使用了-v定义了两个变量

‘prog’

分割后,需要执行prog对其作出处理

echo "abc:def:ghi" | awk -F ":" '{print $1}'

上面的例子中prog = {print $1},其含义是打印出分割后该行的第一列元素。

-f progfile

awk处理的语句除了可以直接写在命令中,还可以在文件中。-f就是指明处理的脚本,分割后的数据会传入到该脚本中进行处理。

新建文件test.awk,写入如下的内容,

#!/usr/bin/awk

# 定义求和函数
function sum(a, b, c) {
    return a + b + c
}

# 常规处理模块
BEGIN {
    total = 10
}

{
    total += sum($1, $2, $3)
}

END {
    print total
}

运行下面的命令,

echo "1 2 3" | awk -f test.awk

输出

16

上面的命令从把1 2 3分割成[1, 2, 3],然后读取文件test.awk中的命令对[1, 2, 3]做处理。test.awk做了两件事请

  • 定义函数sum
  • 初始化值total = 10,然后调用sum对1,2,3求和,然后将sum的结果加上total

awk语法

代码结构

BEGIN {}  # 初始化代码块
{}        # 处理代码块
END {}    # 结尾代码块
  • BEGIN用于初始化工作
  • END用于结尾工作
  • 中间的代码块用于处理输入的每一行数据

条件语句

if( expression ) statement [ else statement ]

例子

echo "1 2 3" | awk '{if ($1 > 1) {print $1} else {print $2} }'

输出 2

循环语句

while( expression ) statement
for( expression ; expression ; expression ) statement
for( var in array ) statement
do statement while( expression )
break
continue

例子

cho "1 2 3" | awk 'BEGIN {i=1} {while(i < 4) { if ($i > 1) print $i; i++}}

输出

2
3

上面的例子中遍历每一列,如果列值大于1则打印出列值,所以打印出了2和3。上面命令等价于

for

echo "1 2 3" | awk '{for (i=1; i<4; i++) { if ($i > 1) print $i}}

do while

echo "1 2 3" | awk 'BEGIN{i=1} {do {if ($i > 1) print $i; i++} while (i < 4)}'

for in

echo "1 2 3" | awk 'BEGIN{a[1]=1; a[2]=2; a[3]=3; }{for (i in a) {if ($i > 1) print $i}}'

操作符

+    # 加法
-    # 减法
*    # 乘法
/    # 除法
%    # 取余
^    # 次方
!    # 非
++   # 自增
--   # 自法
+=   # 加等
-=   # 法等
*=   # 乘等
/=   # 除等
%=   # 取余等
^=   # 次方等
>    # 大于
>=   # 大于等于
<    # 小于
<=   # 小于等于
==   # 等于
!=   # 不等于
?:   # 双目选择符
[]   # 数组取值
||   # 逻辑或
&&   # 逻辑与
in   # 数组遍历
$    # 取值
~    # 模式匹配(正则匹配)
!~   # 模式不匹配

例子

echo "1 2 3" | awk '{print ($2!=2)?1:2}'

如果$2不等于2则输出1,否则输出2。输出结果为 2

正则表达式

在上面的表达式中讲到了一个运算符”~”(用于模式匹配),awk允许使用该操作符来判断某个属性或者字符串是否匹配某个模式。该运算符的用法是

expression ~ /regulara/ {action}

expression表示需要判断的目标属性或者字符串,regular代表正则表达式, action代表满足条件时需要进行的处理。举个例子: 如果第一列的数据是任意个数小写字母的组合(个数不为0),则打印出第一列的数据

echo "abcdef" | awk '$1 ~ /^[a-z]+$/ {print $1}'

上面的例子会输出 abcdef,如果修改为下面的命令则不会有任何输出,

echo "abcdef0" | awk '$1 ~ /^[a-z]+$/ {print $1}'

awk使用的正则表达式是extended regular expressions,这里大致介绍一下语法

^               # 字符串的开头
$               # 字符串的结尾
.               # 匹配任何符串
[c1c2c3...]     # 包含在c1c2c3...中的任意字符
[^c1c2c3...]    # 不包含在c1c2c3...中的任意字符
|               # 或者
()              # 提供group操作, 可将括号内的数据当做一部分
*               # 任意个数
+               # 大于0的任意个数
?               # 0或者1个
echo "abcdef0" | awk '$1 ~ /^[a-z]+$|^[a-zA-Z0-9]*$/ {print $1}'

上面的例子输出为 abcdef0, 这里用到了”^”, “$”, “[]”, “|”, “*”, “+”

echo "abcdef" | awk '$1 ~ /^(abcdef)?$/ {print $1}'

上面的例子输出为 abcdef,这里用到了”^”, “$”, “()”, “?”

内置函数

  1. gsub(r,s,t)

    t中所有满足正则表达式r的子串都用s来进行替换,函数返回被替换的次数。如果t不指定,则默认使用$0,也就是整行数据。

    echo "abc123" |awk '{gsub(/[0-9]+/, "replace", $1); print $1}' 
    

    上面的例子中对数字子串进行了替换,输出为

    abcreplace
    
  2. index(s, t)

    返回子串t在目标串s中的开始下标,如果没有找到则返回0,s的第一个字符的下标是1。

    echo "abc123" |awk '{i=index($i, "123"); print i}' 
    

    输出为 4

  3. length(s)

    返回字符串s的长度。

    echo "abc123" | awk '{print length($1)}'
    

    输出为 6

  4. match(s, r) 返回s中满足正则表达式r的子串的第一个下标。
    echo "abc123" | awk '{print match($1, /[0-9]+/)}'
    

    输出为 4

  5. split(s, A, r)

    使用正则表达式对s进行分割,分割后的数组存放在A中。如果r不指定,则采用fs(-F参数指定的分隔符)进行分割。

    echo "abc123def" | awk '{split($1, A, /[0-9]+/); for (i in A) print A[i]}'
    

    输出为

    abc
    def
    
  6. sprintf

    格式化输出

    echo "abc123" | awk '{print sprintf("hh%s", $1)}'
    

    输出为 hhabc123

  7. sub(r, s, t)

    功能与gsub相类似,不过只能处理一个匹配,之后的匹配不做任何处理

  8. substr(s, i, n)

    返回字符串s从i开始长度为n的子串

    echo "abc123" | awk '{print substr($1, 1, 3)}'
    

    输出为 abc

  9. tolower(s)

    转变为小写字母

  10. toupper()

    转变为大写字母

  11. atan2
  12. cos
  13. exp
  14. int
  15. log
  16. rand

    随机数

  17. sin
  18. sqrt
  19. srand

    设置随机数种子

内置变量

ARGC      命令行参数的个数 
ARGV      命令行参数数组
CONVFMT   内置的用于将数字格式化成string的模板,默认是"%.6g"
ENVIRON   环境变量数组
FILENAME  当前文件名,也就是需要进行数据处理的文件名。如果没有指定文件名,则默认返回"-"。
FNR       当前文件处理的行数
FS        分隔符
NF        当前记录分割后的列数
NR        所有文件处理的行数
OFMT      打印数字的模板,默认是"%.6g".
OFS       列输出的时候插在每列之间的字符串
ORS       每行输出后所添加的字符串
RLENGTH   上一次调用match()函数所匹配出的字符串的长度
RS        输入的所有行的分隔符,默认是"\n"
RSTART    上一次调用match()函数所匹配出的字符串在原始字符串中的开始下标
SUBSEP    数组访问时可以使用多条件,SUBSEP就是多条件的分割,默认是"\034"

  1. OFS的用法
    echo "abc 123 222" | awk 'BEGIN{OFS="$"} {print $1,$2,$3}'
    

    输出为 abc$123$222

  2. ORS的用法
    echo "abc 123 222" | awk 'BEGIN{OFS="$"; ORS="*"} {print $1,$2,$3}'
    

    输出为 abc$123$222*

  3. FNR, NR
    echo -e 'abc 123 222 \n abc 123 222' | awk '{print FNR; print NR;}'
    
  4. RS
    echo -e 'abc 123 222 * abc 123 222' | awk 'BEGIN{RS="*"} {print FNR}'
    

    通过修改RS为字符串”*“,输入的文本被当做了两行。

  5. SUBSEP
    echo -e 'abc 123 222' | awk '{a[1]=$1; a[2]=$2; a[1,2] = $1$2; print a[1 SUBSEP 2]}'
    

    输出为 abc123

gawk

在GNU中还有一个扩展的awk->gawk,在大致功能上与普通的awk相似,除了以下的几个方面

  1. 强大的内置函数

    gawk内置了十分丰富的函数,所以gawk所能实现的功能比awk要丰富。

    举个例子,在调用rand生成随机数的时候采用的是固定的随机序列,我们多次调用下面的命令得到的结果是一样的,

     echo 'aa' | awk '{print rand(); print rand(); print rand();}'
    

    如果我们需要打破这个规则使得上面的例子每次都输出不用的结果,就需要使用srand指定seed,比如

    echo 'aa' | awk '{srand(23); print rand(); print rand(); print rand();}'
    

    但是这样还是没有从根本上解决问题,因为我们的seed每次是固定的,如果真的想做到随机,就需要每次生成不同的seed,这时候gawk就派上用场了,gawk中内置了systime()函数,该函数会返回从19700101开始的秒数,这样在大多数情况下seed都是不同的,这样也就真的实现了随机。

    echo 'aa' | gawk '{srand(systime()); print rand(); print rand(); print rand();}'
    
  2. 正则表达式的增强

    比如下面的场景使用awk就无法匹配出,而使用gawk就可以

    echo "124" | awk '$1 ~ /^\w+$/ {print $1}'
    
  3. 内置变量增
  4. 语法结构

    新增了BEGINFILE和ENDFILE表达式,用于在进入文件和离开文件时进行操作。

    awk 'BEGINFILE {print "enter file "FILENAME} ENDFILE {print "leave file "FILENAME }' 111 222
    

    输出

    enter file 111
    leave file 111
    enter file 222
    leave file 222
    
  5. 其他

场景

总的来说awk适用于结构化输出的数据,比如tomcat的access日志,对于这种结果化的数据awk可以很方便的定位到某一列,然后做出某些操作比如打印,统计等。而对于非结构化的数据awk就不那么适合了,如果我们需要的数据不是固定在某一列,那么awk就需要添加很多操作去进行筛选,那么使用awk就有点得不偿失了