awk入门

来源:互联网 发布:认知神经科学书籍知乎 编辑:程序博客网 时间:2024/05/16 10:28

【题记】 shell 中的 awk 工具十分强大,熟练掌握也不是一朝一夕的。本人参考了一份 awk 手册(原文有点罗嗦),结合自己的实践写了如下内容,作为初学者的参考。

一、为什么要使用 awk ?

      awk 可以用来对文本中的行、列数据进行处理,即格式化文本或抽取数据,性能优异且十分灵活!awk语言的最基本功能是在文件或者字符串中基于指定规则浏览和抽取信息,抽取信息之后才能进行其他文本操作。

二、如何执行 awk ?

[命令] awk [ -F field-separator] 'commands' input-files   或者 awk -f awk-script-file input-files (在一个文件中调用另一个awk脚本)

[解释] awk 会先编译该程序,然后处理数据

awk 的主要结构如 Pattern { action },所以常见的 awk 程序都会形如这样:

Pattern1 { Actions1 }

Pattern2 { Actions2 }

......

Pattern3 { Actions3 }

举个 awk 命令的例子来说明 pattern 和 action 到底是什么!

awk ' $1>0  { print $0 } ' data.f
在这个 awk 命令中  "$1>0" 就是 pattern ,中括号里的"print $0" 就是 action 了。

Pattern 是什么?

awk 可接受许多不同型态的 Pattern.一般常使用 "关系表达式"(Relational expression) 来当成 Pattern.
例如:

x > 34 是一个Pattern, 判断变量 x 与 34 是否存在大于的关系.

x == y 是一个Pattern, 判断变量 x 与变量 y 是否存在等于的关系.

上式中 x >34 , x == y 便是典型的Pattern.

awk 提供 C 语言中常见的关系运算符(Relational Operators) 如 >, <, >=, <=, ==, != 。此外, awk 还提供 ~ (match) 及 !~(not match) 二个关系运算符(注一).
其用法与涵义如下:

若 A 为一字符串, B 为一正则表达式(Regular Expression)

A ~ B 判断 字符串A 中是否 包含 能匹配(match)B表达式的子字符串.

A !~ B 判断 字符串A 中是否 不包含 能匹配(match)B表达式的子字符串.

例如 :

"banana" ~ /an/ 整个是一个Pattern。注意正则表达式要放在 / .... /  中!

因为"banana"中含有可以匹配 /an/ 的子字符串, 故此关系式成立(true),整个Pattern的值也是true。

(注一:) 有少数awk论著, 把 ~, !~ 当成另一类的 Operator,并不视为一种 Relational Operator. 本手册中将这两个运算符当成一种 Relational Operator.


Action 是什么?

Actions 是由许多awk指令构成. 而awk的指令与 C 语言中的指令十分类似.
例如 :

awk的 I/O指令 : print, printf( ), getline...

awk的 流程控制指令 : if(...){..} else{..}, while(...){...}...

例如:

awk  ' { if($1<0) {$1="a";$2="b"} else print $0 } '  data.f

如果执行 awk 命令过程中出错,则有可能是以下几种原因:

  • 确保整个awk命令用单引号括起来了
  • 确保命令内所有引号成对出现
  • 确保用花括号括起动作语句,用圆括号括起条件语句
  • 可能忘记使用花括号

awk 如何处理 Pattern { Actions } ?

awk 会先判断(Evaluate) 该 Pattern 的值, 若 Pattern 判断后的值为true (或不为0的数字,或不是空的字符串), 则 awk将执行该 Pattern 所对应的 Actions.反之, 若 Pattern 之值不为 true, 则awk将不执行该 Pattern所对应的 Actions.

例如 : 若awk程序中有下列两指令

50 > 23 {print "Hello! The word!!" }

"banana" ~ /123/ { print "Good morning !" }

awk会先判断 50 >23 是否成立. 因为该式成立, 所以awk将印出"Hello! The word!!". 而另一 Pattern 为 "banana" ~/123/, 因为"banana" 内未含有任何子字符串可 match /123/, 该 Pattern 之值为false, 故awk将不会印出 "Good morning !"

有时语法 Pattern { Actions }中, Pattern 部分被省略,只剩 {Actions}.这种情形表示 "无条件执行这个 Actions".

三、awk 的字段变量

awk 所内建的字段变量及其涵意如下 :

$0 : 当前读入行,表示为字符串
$1 : 第一个字段,即根据分隔符将$0分割后的第一段
$2 : 第二个字段
……
如此类推。

[Q1] 读入数据行时,awk 如何更新这些内建字段变量?

[A1] 当awk读入一个数据行时,记录在$0中。每当 $0 被改动,比如读入下一行内容,awk 会立刻重新对内建变量赋值!如 $0、$1……。

awk中常见的内建变量(Built-in Variables)

NF( Number of Fields ) : awk 读入一行数据的字段数,通俗地说,就是一行数据被划分成了几段?便于对各字段进行遍历
NR( Number of Records ) : awk 已读入的行数,相当于一个计数器。
RS( Record Separator ) : 行分隔符。awk从文件上读取资料时, 将根据 RS 的定义把资料切割成许多Records,而awk一次仅读入一个Record,以进行处理。预设值是'\n'
FS( Field Separator ) : 列分割符。决定了怎么将一行划分为几段。预设值是 空白符(空白和Tab)
FILENAME : awk 正在处理的数据文件名

例如:

data.f 如下:a:b  c:d  123q:w  d:e  234awk '{print $0,$1,$2,NF,NR}' data.f
[解释]

默认 RS='\n' ,也就是根据换行符来读入一个 record 。当 awk 读入一行值,比如此处的 " a:b  c:d  123 " 时,可以得到如下的值

$0 =  a:b  c:d  123
$1 =  a:b
$2 =  c:d

NF = 3
NR = 1

如果将 FS改为 FS="[ :]+" ,即任何由 空格 和 冒号(:) 所组成的字符串都能当作分隔符。比如,执行如下命令

awk 'BEGIN { FS="[ :]+" } { print $0,$1,NR,NF} ' data.f

可以得到如下值(非真实输出,仅作例子):
$0 = a:b  c:d  123
$1 = a
$2 = b
$3 = c
$4 = d
$5 = 123
NR = 1
NF = 5

三、awk的工作流程 :

执行awk时, 它会反复进行下列三个步骤.

  • 自动从指定的数据文件中读取一个数据行,自动更新(Update)相关的内建变量之值. 如 : NF, NR, $0...
  • 依次执行程序中 所有 的 Pattern { Actions } 指令
  • 当执行完程序中所有 Pattern { Actions } 时, 若数据文件中还有未读取的数据, 则反复执行步骤1到步骤4
打印文件中指定的字段数据并加以计算
awk 处理数据时, 它会自动从数据文件中一次读取一笔记录, 并会将该数据切分成一个个的字段; 程序中可使用 $1, $2,... 直接取得各个字段的内容. 这个特色让使用者易于用 awk 编写 reformatter 来改变量据格式。
[ 范例 :] 以文件 emp.dat 为例, 计算每人应发工资并打印报表.
[ 分析 :] awk 会自行一次读入一列数据, 故程序中仅需告诉awk 如何处理所读入的数据行.

awk '{ print $2, $3 * $4 }' emp.dat

执行结果如下 :

Jenny 21000Dan 23650Max 27170John 27500Linda 19950

四、选择符合指定条件的记录

Pattern { Action }为awk中最主要的语法. 若某Pattern之值为真则执行它后方的 Action. awk中常使用"关系表达式" (Relational Expression)来当成 Pattern.

awk 中除了>, <, ==, != ,...等关系运算符( Relational Operators )外,另外提供 ~(match),!~(Not Match) 二个关系运算符. 利用这两个运算符, 可判断某字符串是否包含能匹配所指定正则表达式的子字符串. 由于这些特性, 很容易使用awk来编写需要字符串比对, 判断的程序.
[ 范例 :] 承上例,
组装部门员工调薪5%,(组装部门员工之ID以"A"开头)
所有员工最后之薪资率若仍低于100, 则以100计.
编写awk程序打印新的员工薪资率报表.
[分析 ] : 这个程序须先判断所读入的数据行是否合于指定条件, 再进行某些动作.awk中 Pattern { Actions } 的语法已涵盖这种 " if ( 条件) { 动作} "的架构. 编写如下之程序, 并取名 adjust1.awk

$1 ~ /^A.*/ { $3 *= 1.05 } $3<100 { $3 = 100 }{ printf("%s %8s %d\n", $1, $2, $3)}
[命令]   $awk -f adjust1.awk emp.dat

[结果] 

A125 Jenny 105
A341 Dan 115
P158 Max 130
P148 John 125
A123 Linda 100

[说明] 

awk的工作程序是: 从数据文件中每次读入一个数据行, 依序执行完程序中所有的 Pattern{ Action }指令:
$1~/^A.*/ { $3 *= 1.05 }
$3 < 100 { $3 = 100 }
{printf("%s %8s %d\n",$1,$2,$3)}
再从数据文件中读进下一笔记录继续进行处理.
第一个 Pattern { Action }是: $1 ~ /^A.*/ { $3 *= 1.05 }
$1 ~ /^A.*/ 是一个Pattern, 用来判断该笔数据行的第一栏是否包含以"A"开头的子字符串. 其中 /^A.*/ 是一个Regular Expression, 用以表示任何以"A"开头的字符串. 
Actions 部分为 $3 *= 1.05
$3 *= 1.05 与 $3 = $3 * 1.05 意义相同. 运算子"*=" 之用法则与 C 语言中一样. 此后与 C 语言中用法相同的运算子或语法将不予赘述.

第二个 Pattern { Actions } 是: $3 <100 {$3 = 100 } 若第三栏的数据内容(表薪资率)小于100, 则调整为100.
第三个 Pattern { Actions } 是: {printf("%s %8s %d\n",$1, $2, $3 )} 省略了Pattern(无条件执行Actions), 故所有数据行调整后的数据都将被印出.


五、awk 中的数组

awk程序中允许使用字符串当做数组的下标(index),但注意数组的下标是唯一的,不允许相同。 利用这个特色十分有助于资料统计工作.(使用字符串当下标的数组称为Associative Array)

首先建立一个数据文件, 并取名为 reg.dat. 此为一学生注册的资料文件; 第一栏为学生姓名, 其后为该生所修课程.

Mary O.S. Arch. Discrete
Steve D.S. Algorithm Arch.
Wang Discrete Graphics O.S.
Lisa Graphics A.I.
Lily Discrete Algorithm

awk中数组的特性
>使用字符串当数组的下标(index).
>使用数组前不须宣告数组名及其大小.

例如: 希望用数组来记录 reg.dat 中各门课程的修课人数。这情况,有二项信息必须储存:

(a) 课程名称, 如: "O.S.","Arch.".. ,共有哪些课程事先并不明确.
(b)各课程的修课人数. 如: 有几个人修"O.S."

在awk中只要用一个数组就可同时记录上列信息. 其方法如下:
使用一个数组 Number[ ] :
以课程名称当 Number[ ] 的下标.

以 Number[ ] 中不同下标所对映的元素代表修课人数.
例如:

有2个学生修 "O.S.", 则以 Number["O.S."] = 2 表之.
若修"O.S."的人数增加一人,则 Number["O.S."] = Number["O.S."] + 1 或 Number["O.S."]++ .

如何取出数组中储存的信息

以 C 语言为例, 声明 int Arr[100]; 之后, 若想得知 Arr[ ]中所储存的数据, 只须用一个循环, 如 :
for(i=0; i<100; i++) printf("%d\n", Arr[i]);即可

上式中:
数组 Arr[ ] 的下标 : 0, 1, 2,..., 99
数组 Arr[ ] 中各下标所对应的值 : Arr[0], Arr[1],...Arr[99]

但 awk 中使用数组并不须事先宣告. 以刚才使用的 Number[ ] 而言, 程序执行前, 并不知将来有哪些课程名称可能被当成 Number[ ] 的下标。awk 提供了一个指令, 藉由该指令awk会自动找寻数组中使用过的所有下标. 以 Number[ ] 为例, awk将会找到 "O.S.", "Arch.",...

使用该指令时, 须指定所要找寻的数组, 及一个变量. awk会使用该的变量来记录从数组中找到的每一个下标. 例如
for(course in Number){....}
指定用 course 来记录 awk 从Number[ ] 中所找到的下标. awk每找到一个下标时, 就用course记录该下标之值且执行{....}中之指令. 藉由这个方式便可取出数组中储存的信息.
(详见下例)

[ 范例 : ] 统计各科修课人数,并印出结果.建立如下程序,并取名为 course.awk:

{ for( i=2; i <= NF; i++) Number[$i]++ }END{for(course in Number) printf("%10s %d\n", course, Number[course] )}

[命令]  $awk -f course.awk reg.dat

[结果] 

Graphics 2
O.S. 2
Discrete 3
A.I. 1
D.S. 1
Arch. 2
Algorithm 2

[说明] 这个程序有两个 pattern {action}

第一个Pattern { Actions }指令中省略了Pattern 部分. 故随着每笔数据行的读入其Actions部分将逐次无条件被执行。
以awk读入第一笔资料 " Mary O.S. Arch. Discrete" 为例, 因为该笔数据 NF = 4(有4个字段), 故该 Action 的for Loop中 i = 2,3,4。
i $i 最初 Number[$i] Number[$i]++ 之后
i=2时 $i="O.S." Number["O.S."]的值从默认的0,变成了1 ;
i=3时 $i="Arch." Number["Arch."]的值从默认的0,变成了1 ;
同理,i=4时 $i="Discrete" Number["Discrete"]的值从默认的0,变成了1 ;

第二个 Pattern { Actions }指令中END 为awk之保留字, 为 Pattern 的一种。END 成立(其值为true)的条件是: "awk处理完所有数据, 即将离开程序时. " 。平常读入数据行时, END并不成立, 故其后的Actions 并不被执
行; 唯有当awk读完所有数据时, 该Actions才会被执行 ( 注意, 不管数据行有多少笔, END仅在最后才成立, 故该Actions仅被执行一次.)。

BEGIN 与 END 有点类似, 是awk中另一个保留的Pattern.
唯一不同的是: "以 BEGIN 为 Pattern 的 Actions 于程序一开始执行时, 被执行一次."
NF 为awk的内建变量, 用以表示awk正处理的数据行中, 所包含的字段个数.

awk程序中若含有以 $ 开头的自定变量, 都将以如下方式解释 :
以 i= 2 为例, $i = $2 表第二个字段数据. ( 实际上, $ 在 awk 中为一运算符(Operator), 用以取得字段数据.)

【后记】 摘自 http://www.aslibra.com/doc/awk.htm 。感谢原作者的无私奉献!写得很不错,先学习,多用才能熟悉。以后会写些例子。。

(全文完)

原创粉丝点击