一、awk介绍

Linux文本处理工具三剑客:grep、sed和awk。其中grep是一种文本过滤工具,sed是文本行编辑器,而awk是一种报表生成器,就是对文件进行格式化处理的,这里的格式化不是文件系统的格式化,而是对文件内容进行各种“排版”,进而格式化显示。

在Linux之上我们使用的是GNU awk 简称gawk,并且gawk是awk的链接文件。gawk是一种过程式编程语言。gawk还支持条件判断、数组、循环等各种编程语言中所有可以使用的功能,因此还可以把gawk称为一种脚本语言解释器。

二、awk用法

一个awk通常有BEGIN语句块、能够使用模式匹配的通用语句块、END语句块三部分组成,这三个部分是可选的,任意一个部分都可以不出现在脚本中。

2.1 基本用法:

awk [options] ‘program’ var=value file…

awk [options] -f programfile var=value file…

awk [options] 'BEGIN{ action;… } pattern{ action;… } END{ action;… }' file

其中,program:PATTERN{ACTION STATEMENTS}

program:编程语言

    通常是被单引号或双引号中

BEGIN/END:特殊模式

BEGIN:

    在文件格式化操作开始之前事先执行的一次操作;通常用于输出表头或做出一个预处理操作

END:

    在文件格式操作完成后,命令退出之前执行的一次操作;通常用于输出表尾或做出清理操作

pattern:模式
    决定动作语句何时触发及触发事件
ACTION STATEMENTS:动作语句

    对数据进行处理,放在“{}”内指明,可以是有多个语句组成,各语句间使用分号间隔;如print,printf。

OPTIONS:

支持的选项
说明

-f /PATH/FROM/AWK_SCRIPT  --file program-file

从文件接收 awk指令,可以同时指定多个文件

-F []

--field-separator fs

指定(fs)列分隔符,指明输入时用到的字段分隔符

-v var=value

--assign var=value

为 BEGIN 块定义变量var,指定其值为value,自定义变量,变量赋值

2.2 分隔符、域和记录

    awk执行时,由分隔符分隔的字段(域)标记$1,$2...$n成为域标识;$0为所有域

    文件的每一行称为记录,也可以用别的作为记录的分隔符

    省略action,默认执行print $0

2.3 工作原理

第一步:执行BEGIN{commands}语句块中的语句;

第二步:从文件或标准输入(stain)读取一行,然后执行pattern{commands}语句块,它逐行扫描,从第一行到最后一行重复这个过程,直到文件被完全读取。

第三步:当读到输入流末尾时,执行END{commands}语句块。

解释说明:

    BEGIN语句块在awk开始从输入流中读取之前被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中。

    pattern语句块中的通用命令时最重要的部分,它也是可选的,如果没有提供pattern语句块,则是默认执行{print}。既打印每一个读取到的行,awk读取的每一行都会被执行该语句块。

    END语句块在awk从输入流中读取完所有的行之后就被执行,比如打印所有行的 分析结果这类信息汇总都是在END语句块中完成,他也是一个可选的语句块。

三、介绍

3.1 awk的输出命令之一:print

用法:

print item1,iitem2,...

    item:字符串,用引号引用;

    变量:显示变量的值,可以直接使用变量的名进行引用;

    数值:无须加引号

要点:

(1)各item之间需要使用逗号分隔,而输出时的分隔符默认为空白字符;

(2)输出的各item可以为字符串或数值、当前记录的字段($#)、变量或awk的表达式,数值会被隐式转换成字符串进行输出;

(3)print后面的item省略时,相当于运行“print $0”,用于输出整行;

(4)输出空白字符:print“”

示例:

tail -3 /etc/fstab |awk '{print $2,$4}'

_N8$R7{[)8_8U_DTB2]35QB.png

echo 1 2 3 |awk '{print $1,$2}'

GGBO7GJRV21PE({W{Z(S8W5.png

3.2 变量

    变量:内置和自定义变量

3.2.1 内置变量

    允许使用美元符号($)和数据字段在数据行中位置对应的数值来引用该数据行中的字段

FS:输入字段分隔符,默认为空白字符

    -v FS="[ , : . ]" 相当于 -F:

示例:

awk -v FS=':' '{print $1,FS,$3}’ /etc/passwd

KVB4DYV`C2)N_4(]S13(%%V.png

awk –F: '{print $1,$3,$7}’ /etc/passwd

KI)JAI12I]1([W@HQLC5IBJ.png

OFS:输出字段分隔符,默认为空白字符

awk -v FS=‘:’ -v OFS=‘:’ '{print $1,$3,$7}’ /etc/passwd

II_EXKIRXS1H68UF_C9@MXW.png

RS:输入记录分隔符,指定输入时的换行符,原换行符仍有效

awk -v RS=':' '{print $1,$3,$7}' /etc/passwd

PU%8KV`0(~FTHSGT_9GA6AU.png

ORS:输出记录分隔符,输出时用指定符号代替换行符

awk -v RS=':' -v ORS='###'‘{print }’ /etc/passwd

PXMDRYBPC(MTXBIGQR(}`1C.png

NF:当前行的字段数量

    print NF:显示当前行的字段数

    print $NF:显示当前行的第NF字段的值

示例:

    显示/etc/passwd每行的字段的数量

awk -F:'{print NF}' /etc/passwd

注:引用内置变量不用$

)9OW5AIV@JG~AF~HD9`[{9F.png

awk -F: '{print $(NF-1)}' /etc/passwd

QA7XU5%I2B6{}8]3LO98MH5.png

NR:行号;命令后跟的所有文件将统一合并计数

示例:

    显示/etc/fstab文件的总行号和最后一行的行号

awk '{print NR}' /etc/fstab ; awk END'{print NR}' /etc/fstab

W$FN}{GS$C@WUAH52O86E[A.png

注:printf默认不换行,需要借助END,而且顺便借助不换行完成单词的拼接,模式数量必须与变量数量(列)对应起来。

FNR:各文件分别计数;行号

示例:

    分别计算/etc/fstab和/etc/inittab两个文件的行号

awk '{print FNR}' /etc/fstab /etc/inittab

_X}}N29AN[ASCGO26}1Y85B.png

FILENAME:当前正在被awk读取的文件的文件名

示例:

    显示当前正在被awk读取的/etc/fstab文件名

awk '{print FILENAME}’ /etc/fstab

M~P@N1XGI75G[9WD`[)R6{3.png

ARGC:awk命令行的参数的个数

示例:

awk '{print ARGC}’ /etc/fstab /etc/inittab

0L@`98}(7SXQTKM@RB51`UB.png

awk ‘BEGIN {print ARGC}’ /etc/fstab /etc/inittab

XD{23F1]{IA93%UA(9NG6QB.png

ARGV:数组,保存的是命令行所给定的各参数

    ARGC[index]

    ARGC[0],ARGC[1]

示例:

awk ‘BEGIN {print ARGV[0]}’ /etc/fstab /etc/inittab

awk ‘BEGIN {print ARGV[1]}’ /etc/fstab /etc/inittab

awk ‘BEGIN {print ARGV[2]}’ /etc/fstab /etc/inittab

8FFBU57K18@_P8HDR~HWGZL.png

注:命令awk也是一个参数。

3.2.2 自定义变量(区分字符大小写)
    (1) -v var=value  变量名区分字符大小写;
    (2) 在program中直接定义

示例:

以/etc/passwd文件中的用户为例,显示这些用户的性别为male,年龄为18。

awk -F: '{sex="male";print $1,sex,age;age=18}' /etc/passwd

(@[C(O)W6XXH(5B84G%LZ23.png

3.3 awk的输出命令之二:printf格式化输出

语法:

    printf FORMAT,item1,item2,...

要点:

    (1)必须提供FORMAT;

    (2)与print语句不同,printf不会自动换行,需要显示指定换行符:\n

    (3)FORMAT中需要分别为后面的每个item指定一个格式符,否则item无法显示。

格式符:都以%开头,后跟单个字符;

printf 输出格式铆定符
格式
铆定符
效果说明
%c
显示字符的ASCII码
%s
显示为字符串
%d, %i
显示为十进制整数
%o
无符号八进制数的整数部分
%e, %E科学计数法显示数值
%u
显示无符号整数
%f
显示为浮点数
%%
显示%符号自身
%10s预留10个字符位置,右对齐
%-10s左对齐显示
%g,%G以科学计数法或浮点形式显示数值

示例:

    在文件/etc/issue中每行前添加“user:”并输出

awk -F: '{printf "user:%s\n",$1}' /etc/issue

spacer.gif{TU`]S`LBC~{BU}6KI{6O0R.png

修饰符:每一种格式都有一些修饰符

    #[.#]:

        左边的#:用于指定显示宽度;

        右边的#:显示精度

    -:左对齐(默认右对齐)

    +:显示数值的正负符号

示例:

    显示文件/etc/passwd中用户名和UID以“Username:%15s,UID:%d\n”形式输出,用户名向右对齐15个字符,UID以十进制整数输出。

awk -F: '{printf "Username:%15s,UID:%d\n",$1,$3}' /etc/passwd

B0`GQ6C7XP$A5TR6FWTB~SG.png

3.4 awk的操作符

    awk的操作符有:算术操作符、字符操作符、赋值操作符、比较操作符、模式匹配操作符、逻辑操作符、条件表达式和函数调用。

算数操作符:实现一些算术运算:
   x+y:加;
   x-y:减;
   x*y:乘;
   x/y:除;
   x^y:x的y次方;
   x%y:取余;x除以y得出的余数
   -x:转换为负值
   +x:转换为数值
  字符串操作符:没有符号的操作符,字符串连接
  赋值操作符:通常为变量的赋值:
   =:等于;
   +=:自加;例,x+=y,意思是把x本身的值加上y的值再赋给x
   -=: 自减;例,x-=y,意思是把x减去y的值再赋值给x
   *=: 自乘;例,x*=y,意思是把x乘以y的值再赋值给x
   /=: 自除;例,x/=y,意思是把x除以y的值再赋值给x
   %=:取余赋值;例,x%=y,意思是x除以y后的余数赋值给x
   ^=: 次方;例,x^=y,意思是把x的y次方的值再赋值给x
   ++:递加;例,i++,意思是把i的值加1再赋值给i,等同于i+=1
   - -: 递减;例,i- -,意思是把i的值减1再赋值给i,等同于i-=1
  比较操作符:字符串或者数值的大小比较:
   = =: 等于
   !=: 不等于
   >: 大于
   >=: 大于等于
   <: 小于
   <= : 小于等于
  模式匹配操作符:根据右侧的模式进行匹配操作:
    ~:左边是否匹配包含右边
    !~: 是否左边和右边是否不匹配包含
  逻辑操作符:进行逻辑的运算:

            &&:与运算

            ||:或运算

            !:非
  条件表达式(三目表达式):
   格式:

     selector?if-true-expression:if-false-expression

  三目表达式顾名思义就是有三个表达式组成的,问号之前的表达式通常为判断表达式,问号之后的表达式是满足条件才会执行的语句,冒号之后的是当不满足条件时才会执行的语句。
示例:

awk -F: '{$3>=1000?usertype="common user":usertype="sysadmin or sysuser";printf "%15s: %-s\n" ,$1,usertype}' /etc/passwd

X@DB{M%RZU~J$NP%(FHVA@U.png

3.5 awk PATTERN
PATTERN:根据pattern条件,过滤匹配的行,再做处理
    (1)如果未指定,empty:空模式,匹配每一行
    (2) /regular expression/:仅处理能够模式匹配到的行,需要用/ /括起来

示例:

    显示/etc/fstab文件中是UUID的行的第一段

awk '/^UUID/{print $1}' /etc/fstab

6QEO6G90@V_62N%T{T)~%8J.png

    显示/etc/fstab文件中没有UUID的行的第一段

 awk '!/^UUID/{print $1}' /etc/fstab

Z$R7TCHZ42G9ANFKKN`_]96.png

    (3) relational expression: 关系表达式,结果为“真”才会被处理
    真:结果为非0值,非空字符串
    假:结果为空字符串或0值

    (4)line ranges:行范围
    startline,endline:/pat1/,/pat2/ 不支持直接给出数字格式

示例:

   awk -F: ‘/^root\>/,/^nobody\>/{print $1}' /etc/passwd

]D[YPNHPZBU{JEK%IE29[W0.png

    显示/etc/passwd文件中10~20的用户名

 awk -F: ‘(NR>=10&&NR<=20){print NR,$1}' /etc/passwd

W61%]8U~CNSA]U~8JR(]I94.png

    (5)BEGIN/END模式
            BEGIN{}: 在文件格式化操作开始之前事先执行的一次操作;通常用于输出表头或做出一个预处理操作;
            END{}:在文件格式化操作完成之后,命令退出之前执行的一次操作;通常用于输出表尾或做出清理操作;

示例:

awk -F: 'BEGIN{print " USER UID \n--------------- "}{print $1,$3}'END{print "=============="} /etc/passwd

FP4I$WB4A_88LXL1C[JNB3A.png

3.6 awk action

常用的action分类

(1) Expressions:算术,比较表达式等
(2) Control statements:if, while等 
(3) Compound statements:组合语句 
(4) input statements 
(5) output statements:print等

3.7 awk控制语句

{ statements;… } 组合语句
if(condition) {statements;…} 
if(condition) {statements;…} else {statements;…}
while(conditon) {statments;…}
do {statements;…} while(condition)
for(expr1;expr2;expr3) {statements;…}
break
continue
delete array[index]
delete array
exit

3.7.1 awk控制语句if-else
语法:

      if(condition)
      {
          statement;…}
        [else statement]
       if(condition1){
         statement1
        }
        else if(condition2)
        {
            statement2
        }else{
            statement3
        }

使用场景:对awk取得的整行或某个字段做条件判断

示例:

    显示磁盘使用率大于20的磁盘名和使用

df -h|awk -F% '/^\/dev/{print $1}'|awk '$NF>=20{print $1,$5}'

HTD2X7}AY@XIJ4IPE41)UIU.png

awk 'BEGIN{ test=100;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
awk 'BEGIN{ test=85;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
awk 'BEGIN{ test=59;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'

M9GN8$CJQNBA@8C0Q}LCZAK.png

注:awk的if语句也支持else子句,允许在if语句条件不成立的情况下执行一条或多条语句,也可以在单行上使用else子句,但必须在if语句部分使用分号“:”.

3.7.2 while循环
语法:

    while(condition){statement;…}

    条件“真”,进入循环;条件“假”,退出循环
使用场景:
    对一行内的多个字段逐一类似处理时使用
    对数组中的各元素逐一处理时使用
示例:

    显示/etc/grub2.cfg文件中linux16/后跟代码的字段的长度

awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg

L68WOQ${7N[PD2OXX~`F`1R.png

awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=10) {print $i,length($i)}; i++}}' /etc/grub2.cfg

$DG75200TJ7KT8(H)(X0T2O.png

3.7.3 do-while循环
do-while语句类似于while语句,但会在检查条件语句之前执行命令。格式为:

   do 
   {
       statement;…
    }while(condition)

意义:无论真假,至少执行一次循环体,这种格式保证了语句会在条件被评估之前至少执行一次。

示例:

    计算1~100相加的值

awk 'BEGIN{ total=0;i=0;do{ total+=i;i++;}while(i<=100);print total}'

O$5$N1EB2Q[$0~~@$RDN}UU.png

3.7.4 for循环
语法:

     for(expr1;expr2;expr3) {statement;…}
    for(variable assignment;condition;iteration process) {for-body}

特殊用法:

    能够遍历数组中的元素
语法:

    for(var in array) {for-body}

示例:

awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg

spacer.gif

3.7.5 switch语句
语法:

   switch(expression) {case VALUE1 or /REGEXP/: statement1; case VALUE2 or /REGEXP2/: statement2; ...; default: statementn}

3.7.6 break和continue

    break [n]:退出当前循环,n是一个数字,用于指定退出几层循环;

    continue:提前结束本轮循环而进入下一轮;

示例:

     计算1~100是偶数相加的值

awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'

2A60[@Q]T{(]`ADDYRFB[FQ.png

awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==66)break;sum+=i}print sum}'

CO6(`UCQS2BM4J}LL(}EH$U.png

3.7.7 next
提前结束对本行文本处理,而提前进入下一行的处理操作(awk自身循环)

示例:

    筛选/etc/passwd中UID能被2整除的数,并输出用户名和UID。

awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd

X{U~L]8QV5NEMS211HQ]1RT.png

四、数组

    awk编程语言使用关联数组来提供数组功能,关联数组跟数字数组不同之处在于它的索引值可以是任意文本字符串。每个索引字符串都必须是唯一的,并唯一地标识赋给它的数据元素。

关联数组:

    array[index-expression]
    index-expression:

    (1) 可使用任意字符串;字符串要使用双引号括起来
    (2) 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空串”;若要判断数组中是否存在某元素,要使用“index in array”格式进行遍历

数组遍历:

    可以用for语句的一种特殊形式

格式:

    for(var in array) 
    {
        for-body
     }

注意:var会遍历array的每个索引,这个for语句也会在每次将关联数组array的下一个索引值赋给变量var时,执行一遍statements。重要的是记住这个变量是索引值而不是数据元素值。

示例:

    统计netstat -tan中各状态的次数

netstat -tan | awk '/^tcp/{state[$NF]++}END{for(i in state) { print i,state[i]}}'

GS$ZTSU2IM0N8C{TW%53(PB.png

注:每出现一被/^tcp/模式匹配到的行,数组S[$NF]就加1,NF为当前匹配到的行的最后一个字段,此处用其值做为数组S的元素索引。

    统计访问本终端的日志中所有IP地址数量

awk '{ip[$1]++}END{for(i in ip) {print i,ip[i]}}' /var/log/httpd/access_log

$2BU)TEC8{B5Q}7LEL1{SJO.png

五、awk函数

    函数分为内建函数和用户自定义函数

5.1 内建函数

数值处理:

    rand():返回0和1之间一个随机数

示例:

    输出10个100以内的随机数

awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }'

2PG]4S9T8GWF{M)U~7HZMT4.png

字符串处理:
    length([s]):返回指定字符串的长度
    sub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并将第一个匹配的内容替换为s

示例:

    把"2008:08:08 08:08:08"中第一个“:”替换成“-”

echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'

)ISA$DZSNZ_XZGNUB8PIT$1.png

    gsub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并全部替换为s所表示的内容

示例:

把"2008:08:08 08:08:08"中的第一段中的“:”全部替换成“-”

echo "2008:08:08 08:08:08" | awk ‘gsub(/:/,"-",$0)'

CYZ{_717@QS4MPYII8XTYON.png

    split(s,array,[r]):以r为分隔符,切割字符串s,并将切割后的结果保存至array所表示的数组中,第一个索引值为1,第二个索引值为2,…

示例:

netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}'

NXFZ)6R%$ZRR)7J89]O}0QP.png

注意:awk的数组下标从1开始编号,而非0.

    substr(s,i[,n]):从 s 所表示的字符串中取子串,取法:从 i 表示的位置开始,取n个字符;

    时间类函数:systime(),取当前时间,结果形式为时间戳;

示例:

    取/etc/passwd中的用户名

awk '{split($0,userinfo,":");print userinfo[1]}' /etc/passwd

%NT9FC%[4HE4}NT[20N~JD8.png

5.2 用户自定义函数

要定义自己的函数,必须使用function关键字:

格式:

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

示例:

    使用awk函数,2和3比大小

cat fun.awk
function max(v1,v2) {
    v1>v2?var=v1:var=v2
return var
}
BEGIN{a=3;b=2;print max(a,b)}
awk –f fun.awk

7NLUU}NX(UG)Z`8Q}CBD6O1.png

注:在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块),而且,不能将-f命令行参数和内联gawk脚本放到一起使用,不过可以在同一个命令行中使用多个-f参数。