天天看点

20分钟 Awk 入门20分钟 Awk 入门

awk是一种小巧的编程语言及命令行工具。(其名称得自于它的创始人alfred aho、peter weinberger 和 brian kernighan姓氏的首个字母)。它非常适合服务器上的日志处理,主要是因为awk可以对文件进行操作,通常以可读文本构建行。

20分钟 Awk 入门20分钟 Awk 入门

在任何情况下,awk都不仅仅只是用来查找数据的(否则,grep或者ack已经足够使用了)——它同样使你能够处理数据并转换数据。

<a target="_blank"></a>

awk脚本的代码结构很简单,就是一系列的模式(pattern)和行为(action):

# comment

pattern1 { actions; }

pattern2 { actions; }

pattern3 { actions; }

pattern4 { actions; }

扫描文档的每一行时都必须与每一个模式进行匹配比较,而且一次只匹配一个模式。那么,如果我给出一个包含以下内容的文件:

this is line 1

this is line 2

this is line 1 这行就会与pattern1进行匹配。如果匹配成功,就会执行actions。然后this is line 1 会和pattern2进行匹配。如果匹配失败,它就会跳到pattern3进行匹配,以此类推。

一旦所有的模式都匹配过了,this is line 2 就会以同样的步骤进行匹配。其他的行也一样,直到读取完整个文件。

简而言之,这就是awk的运行模式

awk仅有两个主要的数据类型:字符串和数字。即便如此,awk的字符串和数字还可以相互转换。字符串能够被解释为数字并把它的值转换为数字值。如果字符串不包含数字,它就被转换为0.

它们都可以在你代码里的actions部分使用 = 操作符给变量赋值。我们可以在任意时刻、任意地方声明和使用变量,也可以使用未初始化的变量,此时他们的默认值是空字符串:“”。

可以使用的模式分为三大类:正则表达式、布尔表达式和特殊模式。

正则表达式和布尔表达式

你使用的awk正则表达式比较轻量。它们不是awk下的pcre(但是gawk可以支持该库——这依赖于具体的实现!请使用 awk

–version查看),然而,对于大部分的使用需求已经足够了:

/admin/ { ... } # any line that contains 'admin'

/^admin/ { ... } # lines that begin with 'admin'

/admin$/ { ... } # lines that end with 'admin'

/^[0-9.]+ / { ... } # lines beginning with series of numbers and periods

/(post|put|delete)/ # lines that contain specific http verbs

布尔表达式与php或者javascript中的布尔表达式类似。特别的是,在awk中可以使用&amp;&amp;(“与”)、||(“或”)、!(“非”)操作符。你几乎可以在所有类c语言中找到它们的踪迹。它们可以对常规数据进行操作。

与php和javascript更相似的特性是比较操作符,==,它会进行模糊匹配(fuzzy matching)。因此“23”字符串等于23,”23″ == 23 表达式返回true。!= 操作符同样在awk里使用,并且别忘了其他常见的操作符:&gt;,&lt;,&gt;=,和&lt;=。

你同样可以混合使用它们:布尔表达式可以和常规表达式一起使用。 /admin/ || debug == true 这种用法是合法的,并且在遇到包含“admin”单词的行或者debug变量等于true时该表达式就会匹配成功。

注意,如果你有一个特定的字符串或者变量要与正则表达式进行匹配,~ 和!~ 就是你想要的操作符。 这样使用它们:string ~ /regex/ 和 string !~ /regex/。

同样要注意的是,所有的模式都只是可选的。一个包含以下内容的awk脚本:

{ actions }

对输入的每一行都将会简单地执行actions。

在awk里有一些特殊的模式,但不是很多。

第一个是begin,它仅在所有的行都输入到文件之前进行匹配。这是你可以初始化你的脚本变量和所有种类的状态的主要地方。

另外一个就是end。就像你可能已经猜到的,它会在所有的输入都被处理完后进行匹配。这使你可以在退出前进行清除工作和一些最后的输出。

最后一类模式,要把它进行归类有点困难。它处于变量和特殊值之间,我们通常称它们为域(field)。而且名副其实。

使用直观的例子能更好地解释域:

# according to the following line

#

# $1 $2 $3

# 00:34:23 get /foo/bar.html

# _____________ _____________/

# $0

# hack attempt?

/admin.html$/ &amp;&amp; $2 == "delete" {

print "hacker alert!";

}

域(默认地)由空格分隔。$0 域代表了一整行的字符串。 $1 域是第一块字符串(在任何空格之前), $2 域是后一块,以此类推。

一个有趣的事实(并且是在大多是情况下我们要避免的事情),你可以通过给相应的域赋值来修改相应的行。例如,如果你在一个块里执行 $0 = “haha the line is gone”,那么现在下一个模式将会对修改后的行进行操作而不是操作原始的行。其他的域变量都类似。

这里有一堆可用的行为(possible actions),但是最常用和最有用的行为(以我的经验来说)是:

{ print $0; } # prints $0. in this case, equivalent to 'print' alone

{ exit; } # ends the program

{ next; } # skips to the next line of input

{ a=$1; b=$0 } # variable assignment

{ c[$1] = $2 } # variable assignment (array)

{ if (boolean) { action }

else if (boolean) { action }

else { action }

{ for (i=1; i&lt;x; i++) { action } }

{ for (item in c) { action } }

这些内容将会成为你的awk工具箱的主要工具,在你处理日志之类的文件时你可以随意地使用它们。

awk里的变量都是全局变量。无论你在给定的块里定义什么变量,它对其他的块都是可见的,甚至是对每一行都是可见的。这严重限制了你的awk脚本大小,不然他们会造成不可维护的可怕结果。请编写尽可能小的脚本。

可以使用下面的语法来调用函数:

{ somecall($2) }

用户定义的函数同样很简单:

# function arguments are call-by-value

function name(parameter-list) {

actions; # same actions as usual

# return is a valid keyword

function add1(val) {

return val+1;

除了常规变量(全局的,可以在任意地方使用),这里还有一系列特殊的变量,它们的的作用有点像配置条目(configuration entries):

begin { # can be modified by the user

fs = ","; # field separator

rs = "n"; # record separator (lines)

ofs = " "; # output field separator

ors = "n"; # output record separator (lines)

{ # can't be modified by the user

nf # number of fields in the current record (line)

nr # number of records seen so far

argv / argc # script arguments

我把可修改的变量放在begin里,因为我更喜欢在那重写它们。但是这些变量的重写可以放在脚本的任意地方然后在后面的行里生效。

以上的就是awk语言的核心内容。我这里没有大量的例子,因为我趋向于使用awk来完成快速的一次性任务。

不过我依然有一些随身携带的脚本文件,用来处理一些事情和测试。我最喜欢的一个脚本是用来处理erlang的崩溃转储文件,形如下面的:

=erl_crash_dump:0.3

tue nov 18 02:52:44 2014

slogan: init terminating in do_boot ()

system version: erlang/otp 17 [erts-6.2] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]

compiled: fri sep 19 03:23:19 2014

taints:

atoms: 12167

=memory

total: 19012936

processes: 4327912

processes_used: 4319928

system: 14685024

atom: 339441

atom_used: 331087

binary: 1367680

code: 8384804

ets: 382552

=hash_table:atom_tab

size: 9643

used: 6949

...

=allocator:instr

option m: false

option s: false

option t: false

=proc:&lt;0.0.0&gt;

state: running

name: init

spawned as: otp_ring0:start/2

run queue: 0

spawned by: []

started: tue nov 18 02:52:35 2014

message queue length: 0

number of heap fragments: 0

heap fragment data: 0

link list: [&lt;0.3.0&gt;, &lt;0.7.0&gt;, &lt;0.6.0&gt;]

reductions: 29265

stack+heap: 1598

oldheap: 610

heap unused: 656

oldheap unused: 468

memory: 18584

program counter: 0x00007f42f9566200 (init:boot_loop/2 + 64)

cp: 0x0000000000000000 (invalid)

=proc:&lt;0.3.0&gt;

state: waiting

=port:#port&lt;0.0&gt;

slot: 0

connected: &lt;0.3.0&gt;

links: &lt;0.3.0&gt;

port controls linked-in driver: efile

=port:#port&lt;0.14&gt;

slot: 112

产生下面的结果:

$ awk -f queue_fun.awk $path_to_dump

message queue length: current function

======================================

10641: io:wait_io_mon_reply/2

12646: io:wait_io_mon_reply/2

32991: io:wait_io_mon_reply/2

2183837: io:wait_io_mon_reply/2

730790: io:wait_io_mon_reply/2

80194: io:wait_io_mon_reply/2

# parse erlang crash dumps and correlate mailbox size to the currently running

# function.

# once in the procs section of the dump, all processes are displayed with

# =proc:&lt;0.m.n&gt; followed by a list of their attributes, which include the

# message queue length and the program counter (what code is currently

# executing).

# run as:

# $ awk -v threshold=$threshold -f queue_fun.awk $crashdump

# where $threshold is the smallest mailbox you want inspects. default value

# is 1000.

begin {

if (threshold == "") {

threshold = 1000 # default mailbox size

procs = 0 # are we in the =procs entries?

print "message queue length: current function"

print "======================================"

# only bother with the =proc: entries. anything else is useless.

procs == 0 &amp;&amp; /^=proc/ { procs = 1 } # entering the =procs entries

procs == 1 &amp;&amp; /^=/ &amp;&amp; !/^=proc/ { exit 0 } # we're done

# message queue length: 1210

# 1 2 3 4

/^message queue length: / &amp;&amp; $4 &gt;= threshold { flag=1; ct=$4 }

/^message queue length: / &amp;&amp; $4 &lt; threshold { flag=0 }

# program counter: 0x00007f5fb8cb2238 (io:wait_io_mon_reply/2 + 56)

# 1 2 3 4 5 6

flag == 1 &amp;&amp; /^program counter: / { print ct ":", substr($4,2) }

你跟上思路没?如果跟上了,你已经了解了awk。恭喜!

原文发布时间:2015-02-09

本文来自云栖合作伙伴“linux中国”