awk - 数据提取和报告工具 (1) 入门

  • 原创
  • Madman
  • /
  • 2018-04-12 09:10
  • /
  • 0
  • 240 次阅读

awk - 数据提取和报告工具 (1) 入门-min.png

Synopsis: awk是一种解释型编程语言。它非常强大,专为文本处理而设计。它的名字来源于其三位作者的姓氏 - Alfred Aho,Peter Weinberger和Brian Kernighan。awk通常用作数据提取和报告工具,例如生成格式化报告。awk语言广泛使用字符串数据类型,关联数组(即按键字符串索引的数组)和正则表达式。程序由一系列规则rule组成(程序也可能包含用户自定义的函数), 每个规则指定一个要搜索的模式pattern和匹配到模式后的动作action,模式和动作至少要指定一个。如果省略pattern,则action将应用于每一条记录(通常是每一行);如果省略action,默认是{ print $0 }将当前记录整体打印输出

awk系列:


1. 初步认识

GNU awk文档

AWK Tutorial

awk示例

既然awk的基本功能是匹配文本并处理,那么当你运行awk时,你需要指定一个awk程序program来告诉awk该做什么。该程序由一系列规则rule组成(程序也可能包含用户自定义的函数,本文最后会讲), 每个规则指定一个要搜索的模式pattern和匹配到模式后的动作action,模式和动作至少要指定一个。如果省略pattern,则action将应用于每一条记录(通常是每一行);如果省略action,默认是{ print $0 }将当前记录整体打印输出。

多条规则默认使用换行符分隔(如果awk是写在shell命令行中的一整行,多条规则用空白字符分隔)。所有动作语句用大括号{ }包括起来,以将其与模式分隔开,如果有多个动作语句,使用;分号分隔。

在awk语言中,注释以#开头,并且一直延续到行尾, #不一定是该行的第一个字符。

pattern { action }
pattern { action1; action2; action3 }
...

Linux系统中使用的是GNU awk,awk命令其实是指向gawk的软链接

# whereis awk
awk: /usr/bin/awk /usr/libexec/awk /usr/share/awk /usr/share/man/man1/awk.1.gz /usr/share/man/man1p/awk.1p.gz
# ls -l /usr/bin/awk
lrwxrwxrwx. 1 root root 4 Aug 10  2017 /usr/bin/awk -> gawk

1.1 语法

(1) awk命令行

有几种方法可以运行awk程序, 如果程序很短,将它包含在运行awk的命令中是最简单的,如下所示:

# awk 'program' input-file1 input-file2...

如果是在shell命令行中,建议使用单引号' '包括整个awk程序,因此shell不会将任何awk字符解释为特殊的shell字符( 引号还会告诉shell将所有程序视为awk的单个参数,并允许程序长度超过一行)。

(2) awk程序文件

当程序很长时,将它放在一个文件中并使用像这样的命令运行通常会更方便:

# awk -f program-file.awk input-file1 input-file2...

不指定输入文件时,awk将读取标准输入(管道,或者键盘上输入的内容,以Ctrl-d结束输入),以下示例实现类似于cat命令的效果:

# awk '{ print }'
等待用户输入

(3) awk脚本

还可以创建可独立运行的awk脚本:

1. 脚本内容
# cat advice 
#! /bin/awk -f

BEGIN { print "Don't Panic!" }

2. 执行
# chmod +x advice
# ./advice 
Don't Panic!

1.2 执行过程

说明:

  • 读取 : awk从输入流(文件,管道或stdin)中读取一行并将其存储在内存中
  • 处理 : 如果该行内容匹配指定的特定模式时,awk就会按顺序在该行执行指定的命令。如果没有指定匹配模式,默认情况下awk在每一行执行命令
  • 循环 : 重复读取和处理步骤,直至输入文件结束

1.3 记录record和字段field

准确的来说,awk是每次读取一条记录record到内存,然后处理它。因为awk中默认的记录分隔符RS是换行符\n,所以通常以文件的每一行表示一条记录。 同时,awk又把记录分隔成多个字段field,默认的字段分隔符FS是空白字符。记录和字段的概念类似于关系数据库中行和列的关系。

  • $0 表示当前整行内容
  • $1$2... 依次表示第1个字段值、第2个字段值...
  • $NF NF是当前正在处理的记录的字段数,所以$NF代表最后一个字段,$(NF-1)代表倒数第二个字段
1. 可以通过修改awk内置变量 RS 来更改记录的分割符
# awk '{ print $0 }' /etc/passwd | head -n 3
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
# awk 'BEGIN { RS=":" } { print $0 }' /etc/passwd | head -n 3
root
x
0

2. 可以通过修改awk内置变量 FS 来更改记录的分割符。默认字段分隔符是空白字符,所以/etc/passwd只有一个字段; 如果改成冒号分隔,就有7个字段,$1表示第1个字段用户名
# awk '{ print $1 }' /etc/passwd | head -n 3
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
# awk 'BEGIN { FS=":" } { print $1 }' /etc/passwd | head -n 3
root
bin
daemon

1.4 常用的pattern

(1) BEGIN程序块(可选)

BEGIN block的语法是BEGIN {awk-commands},其中BEGIN是特殊的pattern,后面是一系列action。BEGIN block只在awk程序启动时执行一次(处理第一个记录前),一般在这里初始化变量或输出表格的头部信息。 BEGIN是awk关键字,因此它必须大写。

1. 演示用的数据文件,邮件列表,包含姓名/电话/email/关系代码(A:熟人,F:朋友,R:亲戚)
# cat mail-list 
Amelia       555-5553     amelia.zodiacusque@gmail.com    F
Anthony      555-3412     anthony.asserturo@hotmail.com   A
Becky        555-7685     becky.algebrarum@gmail.com      A
Bill         555-1675     bill.drowning@hotmail.com       A
Broderick    555-0542     broderick.aliquotiens@yahoo.com R
Camilla      555-2912     camilla.infusarum@skynet.be     R
Fabius       555-1234     fabius.undevicesimus@ucb.edu    F
Julie        555-6699     julie.perscrutabor@skeeve.com   F
Martin       555-6480     martin.codicibus@hotmail.com    A
Samuel       555-3430     samuel.lanceolis@shu.edu        A
Jean-Paul    555-2127     jeanpaul.campanorum@nyu.edu     R

2. 演示用的数据文件,发货量,包含月份/绿色包装箱数目/红色包装箱数目/橙色包装箱数目/蓝色包装箱数目。涵盖去年12月和今年前四个月,以空行分隔两年的数据
# cat inventory-shipped 
Jan  13  25  15 115
Feb  15  32  24 226
Mar  15  24  34 228
Apr  31  52  63 420
May  16  34  29 208
Jun  31  42  75 492
Jul  24  34  67 436
Aug  15  34  47 316
Sep  13  55  37 277
Oct  29  54  68 525
Nov  20  87  82 577
Dec  17  35  61 401

Jan  21  36  64 620
Feb  26  58  80 652
Mar  24  75  70 495
Apr  21  70  74 514

3. 输出表头
# awk 'BEGIN { printf "%-10s %-10s %-31s %-1s\n", "姓名", "电话", "Email", "关系代码" } { print }' mail-list 
姓名         电话         Email                           关系代码
Amelia       555-5553     amelia.zodiacusque@gmail.com    F
Anthony      555-3412     anthony.asserturo@hotmail.com   A
Becky        555-7685     becky.algebrarum@gmail.com      A
Bill         555-1675     bill.drowning@hotmail.com       A
Broderick    555-0542     broderick.aliquotiens@yahoo.com R
Camilla      555-2912     camilla.infusarum@skynet.be     R
Fabius       555-1234     fabius.undevicesimus@ucb.edu    F
Julie        555-6699     julie.perscrutabor@skeeve.com   F
Martin       555-6480     martin.codicibus@hotmail.com    A
Samuel       555-3430     samuel.lanceolis@shu.edu        A
Jean-Paul    555-2127     jeanpaul.campanorum@nyu.edu     R

(2) END程序块(可选)

END block的语法是END {awk-commands},其中END是特殊的pattern,后面是一系列action。END block只在awk程序结束时执行一次(处理完最后一个记录后),一般在这里输出表格的尾部说明信息。 END是awk关键字,因此它必须大写。

# awk '/li/ { print $0 } END { print "awk结束前执行END块, mail-list总共( " NR " )行" }' mail-list
Amelia       555-5553     amelia.zodiacusque@gmail.com    F
Broderick    555-0542     broderick.aliquotiens@yahoo.com R
Julie        555-6699     julie.perscrutabor@skeeve.com   F
Samuel       555-3430     samuel.lanceolis@shu.edu        A
awk结束前执行END块, mail-list总共( 11 )

(3) /regular expression/正则表达式

awk使用扩展的正则表达式,如果匹配,则执行后面的action;如果不匹配,则不执行。

1. 如果当前记录匹配正则表达式/li/,则打印它的第1列
# awk '/li/ { print $1 }' mail-list

(4) relational expression

使用后续要介绍的awk各种操作符,判断条件表达式的结果,0表示假,不执行后续action;非0表示真,执行后续action

1. 打印第1行的第1列
# awk 'NR==1 {print $1}' mail-list

1.5 常用的action

action语句包含在大括号{}中,由大多数语言中常见的赋值语句、条件语句、循环语句、输入/输出语句等组成,详情见awk进阶篇。

1.6 常用的awk内置变量

  • ARGV 命令行参数数组
  • ARGC 命令行参数的个数,即ARGV数组的长度
  • FILENAME 正在被处理的文件名
  • ENVIRON 存有系统环境变量的关联数组
  • RS 记录分隔符,默认为换行符\n
  • FS 字段分隔符,默认为空白字符(可以是连续的空格或tab)
  • ORS 输出时的记录分隔符,默认为换行符\n
  • OFS 输出时的字段分隔符,默认为空白字符
  • NF 每个记录中的字段数
  • NR 目前已输入的总记录数
  • FNR 正在被处理的文件的记录数,与NR不同的是,这个值会是各个文件自己的当前记录数
  • IGNORECASE 默认是0,区分大小写;1不区分
1. 统计目前为止已处理的总输入记录数 NR
# awk '{ print "当前是第 ", NR, " 条记录,姓名是: ", $1 } END { print "Total number of records:", NR }' mail-list 
当前是第  1  条记录,姓名是:  Amelia
当前是第  2  条记录,姓名是:  Anthony
当前是第  3  条记录,姓名是:  Becky
当前是第  4  条记录,姓名是:  Bill
当前是第  5  条记录,姓名是:  Broderick
当前是第  6  条记录,姓名是:  Camilla
当前是第  7  条记录,姓名是:  Fabius
当前是第  8  条记录,姓名是:  Julie
当前是第  9  条记录,姓名是:  Martin
当前是第  10  条记录,姓名是:  Samuel
当前是第  11  条记录,姓名是:  Jean-Paul
Total number of records: 11

2. 显示当前正在处理的文件名 FILENAME,和当前文件的当前记录数 FNR
# awk '{ print "正在处理文件 " FILENAME ",当前是第 " FNR " 条记录, 第1列是 " $1 } END { print "Total number of records:" NR }' mail-list inventory-sh
ipped 正在处理文件 mail-list,当前是第 1 条记录, 第1列是 Amelia
正在处理文件 mail-list,当前是第 2 条记录, 第1列是 Anthony
正在处理文件 mail-list,当前是第 3 条记录, 第1列是 Becky
正在处理文件 mail-list,当前是第 4 条记录, 第1列是 Bill
正在处理文件 mail-list,当前是第 5 条记录, 第1列是 Broderick
正在处理文件 mail-list,当前是第 6 条记录, 第1列是 Camilla
正在处理文件 mail-list,当前是第 7 条记录, 第1列是 Fabius
正在处理文件 mail-list,当前是第 8 条记录, 第1列是 Julie
正在处理文件 mail-list,当前是第 9 条记录, 第1列是 Martin
正在处理文件 mail-list,当前是第 10 条记录, 第1列是 Samuel
正在处理文件 mail-list,当前是第 11 条记录, 第1列是 Jean-Paul
正在处理文件 inventory-shipped,当前是第 1 条记录, 第1列是 Jan
正在处理文件 inventory-shipped,当前是第 2 条记录, 第1列是 Feb
正在处理文件 inventory-shipped,当前是第 3 条记录, 第1列是 Mar
正在处理文件 inventory-shipped,当前是第 4 条记录, 第1列是 Apr
正在处理文件 inventory-shipped,当前是第 5 条记录, 第1列是 May
正在处理文件 inventory-shipped,当前是第 6 条记录, 第1列是 Jun
正在处理文件 inventory-shipped,当前是第 7 条记录, 第1列是 Jul
正在处理文件 inventory-shipped,当前是第 8 条记录, 第1列是 Aug
正在处理文件 inventory-shipped,当前是第 9 条记录, 第1列是 Sep
正在处理文件 inventory-shipped,当前是第 10 条记录, 第1列是 Oct
正在处理文件 inventory-shipped,当前是第 11 条记录, 第1列是 Nov
正在处理文件 inventory-shipped,当前是第 12 条记录, 第1列是 Dec
正在处理文件 inventory-shipped,当前是第 13 条记录, 第1列是 
正在处理文件 inventory-shipped,当前是第 14 条记录, 第1列是 Jan
正在处理文件 inventory-shipped,当前是第 15 条记录, 第1列是 Feb
正在处理文件 inventory-shipped,当前是第 16 条记录, 第1列是 Mar
正在处理文件 inventory-shipped,当前是第 17 条记录, 第1列是 Apr
Total number of records:28

3. 改变输出的字段分隔符,注意$1$7之间有逗号,不能用{ print $1 $7 }
# awk -F":" 'NR!=1{print $1,$7}' OFS="***" /etc/passwd | head -n 3
bin***/sbin/nologin
daemon***/sbin/nologin
adm***/sbin/nologin

4. 参数获取 ARGV ARGC , 注意:下标从0开始
# awk 'BEGIN{ FS=":"; print "ARGC="ARGC; for(k in ARGV){ print k "=" ARGV[k]; } }' /etc/passwd
ARGC=2
0=awk
1=/etc/passwd

5. 获取Linux的环境变量 ENVIRON
# awk 'BEGIN { OFS="="; for(x in ENVIRON){ print x, ENVIRON[x]; } }'
AWKPATH=.:/usr/share/awk
LANG=en_US.UTF-8
......

6. 忽略大小写 IGNORECASE
# awk '/BILL/{ print }' mail-list
# awk 'BEGIN { IGNORECASE=1 } /BILL/{ print }' mail-list
Bill         555-1675     bill.drowning@hotmail.com       A

查看全部的内置变量:

# awk --dump-variables ''
# cat awkvars.out 
ARGC: 1
ARGIND: 0
ARGV: array, 1 elements
BINMODE: 0
CONVFMT: "%.6g"
ERRNO: ""
FIELDWIDTHS: ""
FILENAME: ""
FNR: 0
FPAT: "[^[:space:]]+"
FS: " "
IGNORECASE: 0
LINT: 0
NF: 0
NR: 0
OFMT: "%.6g"
OFS: " "
ORS: "\n"
RLENGTH: 0
RS: "\n"
RSTART: 0
RT: ""
SUBSEP: "\034"
TEXTDOMAIN: "messages"

2. 常用选项

awk可以指定选项Usage: awk [OPTION]... 'program' [argument]... [input-file]...,也可以省略。

2.1 -f progfile, --file=progfile

当前awk代码比较长时,一般将代码放在额外的文件内,并通过-f指定包含awk程序代码的文件

2.2 -F fs, --field-separator=fs

指定字段的分隔符,如果不使用-F选项,也可以在BEGIN代码块中赋值给FS

# awk -F [,:%] '{ print $1"\t"$NF }' /etc/passwd | head -n 3
或者:
# awk 'BEGIN { FS="[,:%]" } { print $1"\t"$NF }' /etc/passwd | head -n 3
或者:
# awk -v FS=[,:%] '{ print $1"\t"$NF }' /etc/passwd | head -n 3
或者:
# awk '{ print $1"\t"$NF }' FS=[,:%] /etc/passwd | head -n 3

2.3 -v var=val, --assign=var=val

自定义变量。

# awk -v name=wangy 'BEGIN{printf "Name = %s\n", name}'
Name = wangy

说明:

除了使用-v给变量赋值外,还可以在awk程序之后定义变量,及BEGIN程序块内定义变量

1. 测试文件
# cat file-one 
line 01 for file one
# cat file-two 
line 01 for file two

2. 通过-v选项设置的变量,BEGIN可以访问
# awk -v age=18 'BEGIN { print "BEGIN:" age } { print "PROCESS:" age } END { print "END:" age }' file-one file-two 
BEGIN:18
PROCESS:18
PROCESS:18
END:18

3. 在awk程序后面,所有输入文件之前定义的定义的变量,只有BEGIN无法访问
# awk 'BEGIN { print "BEGIN:" age } { print "PROCESS:" age } END { print "END:" age }' age=18 file-one file-two 
BEGIN:
PROCESS:18
PROCESS:18
END:18

4. 在两个输入文件之间定义的变量,只有变量后面的输入文件和END可以访问
# awk 'BEGIN { print "BEGIN:" age } { print "PROCESS:" age } END { print "END:" age }' file-one age=18 file-two 
BEGIN:
PROCESS:
PROCESS:18
END:18

5. 在最后一个输入文件之前定义的变量,只有END可以访问
# awk 'BEGIN { print "BEGIN:" age } { print "PROCESS:" age } END { print "END:" age }' file-one file-two age=18
BEGIN:
PROCESS:
PROCESS:
END:18

6. 打印inventory-shipped文件的第4列,mail-list的第2列
# awk '{ print $n }' n=4 inventory-shipped n=2 mail-list

2.4 --lint

输出awk的警告信息

# awk --lint '' 
awk: cmd. line:1: warning: empty program text on command line
awk: cmd. line:1: warning: source file does not end in newline
awk: warning: no program text at all!

2.5 --profile

以更优雅的格式输出awk程序的语法分析信息,如果没指定目标位置,默认输出到awkprof.out中

# awk --profile '/li/ { print $0 } END { print "awk结束前执行END块, mail-list总共( " NR " )行" }' mail-list
Amelia       555-5553     amelia.zodiacusque@gmail.com    F
Broderick    555-0542     broderick.aliquotiens@yahoo.com R
Julie        555-6699     julie.perscrutabor@skeeve.com   F
Samuel       555-3430     samuel.lanceolis@shu.edu        A
awk结束前执行END块, mail-list总共( 11 )# cat awkprof.out 
    # gawk profile, created Tue Apr 10 08:57:07 2018

    # Rule(s)

    /li/ {
        print $0
    }

    # END block(s)

    END {
        print "awk\347\273\223\346\235\237\345\211\215\346\211\247\350\241\214END\345\235\227, mail-list\346\200\273\345\205\261( " NR " )\350\241\214"
    }

3. 简单示例

3.1 打印字段或列

如前所述,awk将记录分隔成多个字段,你可以选择只输出其中的几列数据或整条记录。

# awk '{ print $1 "\t" $3 }' mail-list

3.2 打印整条记录

# awk '{ print $0 }' mail-list

3.3 指定匹配模式

1. 只打印包含li字符串的记录的第1列和第3列
# awk '/li/ {print $1 "\t" $3}' mail-list

2. 只打印第1列大于8个字符的记录,没有指明action,默认是{ print $0 }
# awk 'length($1) > 8' mail-list

3.4 更改字段的顺序

# awk '/li/ {print $3 "\t" $1}' mail-list

3.5 字段运算

# awk '{ print "Total boxes for every month: ", $2 + $3 + $4 + $5 }' inventory-shipped

3.6 统计匹配成功的记录数

# awk '/li/ { ++cnt } END { print "Count = ", cnt }' mail-list 
Count =  4
分类: Linux
标签: awk gawk GNU
未经允许不得转载: LIFE & SHARE - 王颜公子 » awk - 数据提取和报告工具 (1) 入门

分享

作者

作者头像

Madman

如果博文内容有误或其它任何问题,欢迎留言评论,我会尽快回复; 或者通过QQ、微信等联系我

0 条评论

暂时还没有评论.

发表评论前请先登录

专题