AWK教程

通俗的来讲awk更像是一个行列处理器。
awk、sed、grep更适合的方向:
grep 更适合单纯的查找或匹配文本
sed 更适合编辑匹配到的文本
awk 更适合格式化文本,对文本进行较复杂格式处理

基础知识

读(Read)
AWK 从输入流(文件、管道或者标准输入)中读入一行然后将其存入内存中。

执行(Execute)
对于每一行输入,所有的 AWK 命令按顺执行。 默认情况下,AWK 命令是针对于每一行输入,但是我们可以将其限制在指定的模式中。

重复(Repeate)
一直重复上述两个过程直到文件结束。

开始块 BEGIN BLOCK

BEGIN {awk-commands}

类似于java中的static块,在程序启动时执行,且整个过程只执行一次
一般用于初始化一些变量(如分隔符),BEGIN是AWK的关键词,必须大写。
开始块部分是可选的,一个程序可以没有开始块部分。

主体块 BODY BLOCK

/pattern/ {awk-commands}

如上方工作流中展示的那样,每个输入的行都会执行一次主体块的命令
注意,主体块部分没有关键字

结束块 END BOLCK

END {awk-commands}

结束块是在程序结束时执行的代码。 END 也是 AWK 的关键字,它也必须大写。 与开始块相似,结束块也是可选的。

awk命令行

awk [option] file...
# 打印日志文件
awk '{print}' info.log

$0

此变量表示整个输入记录。

[jerry]$ awk '{print $0}' marks.txt
#执行上面的命令可以得到如下的结果:

1)    Amit     Physics    80
2)    Rahul    Maths      90
3)    Shyam    Biology    87
4)    Kedar    English    85
5)    Hari     History    89

$n

此变量表示当前输入记录的第 n 个域,这些域之间由 FS 分割。

[jerry]$ awk '{print $3 "\t" $4}' marks.txt
执行上面的命令可以得到如下的结果:

Physics    80
Maths      90
Biology    87
English    85
History    89

AWK程序文件

除了使用命令外,awk还可以通过读入文本文件中的命令执行
在test.awk文本中写入{print}
执行awk命令

#打印日志文件
awk -f test.awk info.log

基本命令

awk默认分隔符为空格或tab
元素下表从1开始
示例文本

# text
2 this is a test
3 Are you like awk
This's a test
10 There are orange,apple,mongo
awk '{[pattern] action}' {filenames}   # 行匹配语句 awk '' 只能用单引号

匹配文本某一列

awk '{print $1,$4}' log.txt #每行按空格或TAB分割,输出文本中的1、4项
#结果
 2 a
 3 like
 This's
 10 orange,apple,mongo
 
# 格式化输出
 $ awk '{printf "%-8s %-10s\n",$1,$4}' log.txt
 ---------------------------------------------
 2        a
 3        like
 This's
 10       orange,apple,mongo

通过匹配模式输出某一行

awk '/ABCacheLoader/ {print$0}' info.log

如果某行中含有/parttern/中的字符串,则该行会被输出。
如果没有主体块——默认的动作是输出记录(行)。因此上面的效果也可以使用下面简略方式实现,它们会得到相同的结果:

 awk '/ABCacheLoader/' info.log

通过匹配模式输出某一列

前面我们已经看到了,当模式串匹配成功后, AWK 默认会输出整个记录。不过,我们可以让 AWK 只输出特定的域(列)的内容。 例如,下面的这个例子中当模式串匹配成功后只会输出第1列与第3列的内容:

 awk '/ABCacheLoader/ {print$1 "\t" $3}' info.log

其中$1$3的顺序可以随意地调换,即我们能以任意顺序输出各列

计算匹配次数并输出

统计模式串成功匹配文件中行的次数

 awk '/OfflineTaskCacheLoader/{++cnt}END{print "count=",cnt}' info.log

上面这个例子中,每次成功的匹配我们都会增加计数器的值,并在结束块中将该计数器的值输出。 请注意,与其它编程语言不一样的地方在于, AWK 在使用一个变量前不需要特意地声明这个变量。

输出字符数大于18的行

awk 'length($0)>18' info.log

AWK 提供了内置的 length 函数。该函数返回字符串的长度。变量 $0 表示整行,缺失的主体块会执行默认动作,例如,打印输出。 因此,如果一行中字符数超过 18, 则比较的结果为真,该行则被输出。

内置变量

ARGC 参数个数

ARGC 表示在命令行提供的参数的个数。

awk 'BEGIN {print "argc = " ,ARGC}' info.log info.log
argc =  3

ARGV 输入参数的数组

这个变量表示存储命令行输入参数的数组。数组的有效索引是从 1 到 ARGC。其中ARGV[0] = awk

awk 'BEGIN {for (i=0; i<ARGC;i++) {printf "ARGV[%d] = %s\n",i,ARGV[i]}}' one two three
ARGV[0] = awk
ARGV[1] = one
ARGV[2] = two
ARGV[3] = three

ENVIRON 环境变量

获取环境变量
具体的环境变量可以通过系统中的 env 命令查询其它环境变量的名字

awk 'BEGIN{print ENVIRON["PATH"]}'

FILENAME 文件名

值得注意的是在开始块中FILENAME是未定义的

awk 'END{print FILENAME}' info.log
info.log

FS (Field Separator 字段分隔符)

此变量表示输入的数据域之间的分隔符,其默认值是空格。 你可以使用 -F 命令行选项 或者在 BEGIN块中设置分割符 改变它的默认值。

awk 'BEGIN{FS=":"}{print $2}' info.log
#上下等价
awk -F ":" '{print $2}' info.log
awk 'BEGIN{FS=":";print "FS ="FS}' info.log
FS =:

FS和OFS命令
FS:Field Separator,字段分隔符(对输入生效)
OFS:Out of Field Separator,输出字段分隔符(对输出生效)

awk 'BEGIN{ FS=" ";OFS="," }{ print $1,$2,$3,$4 }' file2.txt > file3.txt

NF(Number of Filed)

此变量表示当前输入记录中域的数量。例如,下面这个例子只输出超过两个域的行:
echo -e 激活转义字符。使用-e选项时,若字符串中出现转义字符,则特别加以处理,不会当做一般文字输出

echo -e "one two\nthree\none two three" | awk 'NF >2'
one two three

NR(Number of Row)

该变量记录当前处理的记录的数量。下面例子会输出读入的前2行(NR<3)。

cat info.log|awk 'NR < 3'
#上下等价
awk '{if(NR<3){print}}' info.log

FNR(File Number of Row)

该变量与 NR 类似,不过它是相对于当前文件而言的。此变量在处理多个文件输入时有重要的作用。每当从新的文件中读入时 FNR 都会被重新设置为 0。

awk '{print NR,FNR}' info.log  newtable
# 新文件NR继续计数,而FNR重0开始计数
1029 1029
1030 1030
1031 1
1032 2

OFS(Output Field Separator 输出字段分割符)

此变量表示输出域之间的分割符,其默认为空格
对 {print $1,$2,$3}生效 ,即只对print中的逗号(,)生效

awk 'BEGIN{OFS = "---"}{print $1,$2}' info.log
-----
2020-06-15---20:00:02.446
2020-06-15---20:00:02.457
2020-06-15---20:00:02.462

RS (Row Separator 输入行分隔符)

此变量表示输入记录(行)之间的分隔符,默认为换行符

#以:作为行分割
awk 'BEGIN{RS=":"}{print $1}' info.log | more
-------
2020-06-15
00
02.446
]
49]
00
02.457
]
67]

ORS(行分隔符)

此变量表示输出记录(行)之间的分割符,其默认值是换行符。

awk 'BEGIN{RS=":";ORS="---"}{print $1}' info.log | more
2020-06-15---00---02.446---]---49]---00---02.457---]---67]---00---02.462---]---56]---00---02.468---]

RSTART(匹配字符串)

此变量表示由 match 函数匹配的字符串的第一个字符的位置。

awk 'BEGIN { if (match("One Two Three", "Thre")) { print RSTART } }'
#执行上面的命令可以得到如下的结果:
9

操作符

正则表达式操作符
下面将介绍两种正则表达式操作符:

匹配(Match)

匹配运算符为~。它用于搜索包含匹配模式字符串的域。下面的示例中将输出包括 9 的行:

[jerry]$ awk '$0 ~ 9' marks.txt
执行上面的命令可以得到如下的结果:

2)  Rahul   Maths   90
5)  Hari    History 89

不匹配(Not match)

不匹配操作符为 !~。 此操作符用于搜索不匹配指定字符串的域。如下示例输出不包含 9 的行:

[jerry]$ awk '$0 !~ 9' marks.txt
执行上面的命令可以得到如下的结果:

1)  Amit    Physics 80
3)  Shyam   Biology 87
4)  Kedar   English 85

点(Dot)

点字符(.)可以匹配除了行结束字符的所有字符。比如下面的便子就可以匹配 fin, fun, fan 等等。

echo -e "cat\nbat\nfun\nfin\nfan" | awk '/f.n/'
#输出
fun
fin
fan

行开始

行开始符(^)匹配一行的开始。下面的示例将输出所有以字符串 The 开始的行。

echo -e "this is lipengtest" | awk '/^t/'
this is lipengtest

行结束

行结束符($)匹配一行的结束。下面的例子中将输出所有以字符 t 结束的行:

echo -e "this is lipengtest" | awk '/t$/'
this is lipengtest

匹配字符集

匹配字符集用于匹配集合(由方括号表示)中的一个字符。如下例子中,匹配 Call 与 Tall 而不会匹配 Ball。
echo -e "Call\nTall\nBall" | awk '/[CT]all/'
执行上面的命令可以得到如下结果:
fun
fin
fan

排除集

正则匹配时会排除集合中的字符。如下例子中只会输出 Ball。
注意,^符号只有在集合内才会起作用,且如果放在开头作用范围为整个集合

#放在集合开头
echo -e "Call\nTall\nBall" | awk '/[^CT]all/'
执行上面的命令可以得到如下结果:
Ball
#放在集合中间
echo -e "Call\nTall\nBall" | awk '/[C^T]all/'
Call
Tall
#放在集合外,用作行开始
echo -e "Call\nTall\nBall" | awk '/^[CT]all/'
Call
Tall

竖线(|)允许正则表达式实现逻辑或运算. 下面例子将会输出 Ball 与 Call 。

echo -e "Call\nTall\nBall\nSmall\nShall" | awk '/Call|Ball/'
执行上面的命令可以得到如下结果:
Call
Ball

最多出现一次

该符号(?)前面的字符不出现或者出现一次。如下示例匹配 Colour 与 Color。 使用 ? 使得 u 变成了可选字符 。

echo -e "Colour\nColor" | awk '/Colou?r/'
执行上面的命令可以得到如下结果:
Colour
Color

分组

括号用于分组而字符 | 用于提供多种选择。如下的正则表达式会匹配所有包含 Apple Juice 或 Aplle Cake 的行。

[jerry]$ echo -e "Apple Juice\nApple Pie\nApple Tart\nApple Cake" | awk '/Apple (Juice|Cake)/'
#这里的空格是会算进表达式里的
#执行上面的命令可以得到如下结果:
Apple Juice
Apple Cake

控制流

IF-ELSE语句

if-else语句中允许在条件为假时执行另外一组的动作。下面为 if-else 的语法格式:

if (condition)
    action-1
else
    action-2

其中,条件为真时执行 action-1,条件为假时执行 action-2。下面是使用该语句判断数字是否为偶数的例子:

awk 'BEGIN {num = 11; 
   if (num % 2 == 0) printf "%d is even number.\n", num; 
   else printf "%d is odd number.\n", num 
                    }'

执行上面的操作可以得到如下的结果:

11 is odd number.

EXIT

Exit 用于结束脚本程序的执行。该函数接受一个整数作为参数表示 AWK 进程结束状态。 如果没有提供该参数,其默认状态为 0 。下面例子中当和大于 50 时结束 AWK 程序。

awk 'BEGIN {
    sum = 0; for (i = 0; i < 20; ++i) {
        sum += i; if (sum > 50) exit(10); else print "Sum =", sum 
                                     } 
                    }'
Sum = 0
Sum = 1
Sum = 3
Sum = 6
Sum = 10
Sum = 15
Sum = 21
Sum = 28
Sum = 36
Sum = 45

让我们检查一下脚本执行后的返回状态:

echo $?
执行上面的命令可以得到如下的结果:

19

实际例子

筛选出第n个列并输出到文件中

awk 'BEGIN{FS=";"}{print $1 >> "result1.sql"}' result.txt

传入参数并根据参数匹配输出

#!/bin/bash

#########
#
# 目标:查找文件中对应的sql和riskey
# author: lipeng01_dxm
#
#########


# host="10.21.181.103"
# Port=8888
# user="db_user_rw"
# password="EDCIdek25"
# database="mask"

riskey_array=(
"1"
"2"
"3"
"4"
"5"
"6"
)
shell_var="abc"
for ((i=0; i<6; i++)); do
    shell_var=${riskey_array[i]}
    # awk 'BEGIN{riskey="'$riskey_array[i]'";if($2 == riskey) printf $2}'
    awk 'BEGIN{riskey = "'$shell_var'";}{if($2 == riskey) print $0 >> "/Users/dxm/Desktop/result.txt"}' /Users/dxm/Desktop/rieskeyallsql.txt 
done

匹配模式输出

awk 'BEGIN{FS=";"}{if($1!~/mdm_per_detail_user_info/) {print "--\n" $1 >> /Users/dxm/Desktop/result2.sql}}' result.txt

文件对比

#在awk中,FNR指的是当前文件中的记录号(通常是行号)并NR引用总记录号。运算符==是一个比较运算符,当两个周围的操作数相等时返回true。
#这意味着条件NR==FNR仅适用于第一个文件,因为FNR每个文件的第一行重置为1,但NR会继续增加。
#此模式通常用于仅对第一个文件执行操作。在next块内是指任何进一步的命令被跳过,所以它们仅在比所述第一其他文件运行。
#该条件FNR==NR比较了相同的两个操作数NR==FNR,因此它的行为方式相同。
awk -F',' 'NR==FNR{a[$1]=$1;next}NR!=FNR{if($1 in a){}else{print $0}}' 5568.csv 5569.csv   
awk 'BEGIN{FS = ","}{if(NR==FNR){a[$1]=$1}else{if($1 in a){}else{print $0}}}' 2370.csv  5568.csv

curl命令转换

awk '{print "curl -L -X POST \047//10.157.23.145:8307/mask/_doc/" $1 "\047 -H \047Authorization: Basic ZWxhc3RpYzptYXNrX3Jk\047 -H \047Content-Type: application/json\047 --data-raw \047{}\047"}' 2020-08-12-01.txt > ./2020-08-12-01.sh 
sh ./2020-08-12-01.sh 

单引号因为是关键字,所以需要使用8进制的\047来代替