20分钟 Awk 入门

20分钟 Awk 入门

什么是Awk

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

我说它适用于服务器是因为日志文件,转储文件(dump files),或者任意文本格式的服务器终止转储到磁盘都会变得很大,并且在每个服务器你都会拥有大量的这类文件。如果你经历过这样的情境——在没有像Splunk或者其他等价的工具情况下不得不在50个不同的服务器里分析几G的文件,你会觉得去获取和下载所有的这些文件并分析他们是一件很糟糕的事。

我亲身经历过这种情境。当一些Erlang节点将要死掉并留下一个700MB到4GB的崩溃转储文件(crash dump)时,或者当我需要在一个小的个人服务器(叫做VPS)上快速浏览日志,查找一个常规模式时。

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

代码结构

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


  1. # comment
  2. Pattern1 { ACTIONS; }
  3. # comment
  4. Pattern2 { ACTIONS; }
  5. # comment
  6. Pattern3 { ACTIONS; }
  7. # comment
  8. Pattern4 { ACTIONS; }

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


  1. this is line 1
  2. 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有数组类型,并且它们是动态的一维关联数组。它们的语法是这样的:var[key] = value 。Awk可以模拟多维数组,但无论怎样,这是一个大的技巧(big hack)。

模式

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

正则表达式和布尔表达式

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

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


  1. /admin/ { ... } # any line that contains 'admin'
  2. /^admin/ { ... } # lines that begin with 'admin'
  3. /admin$/ { ... } # lines that end with 'admin'
  4. /^[0-9.]+ / { ... } # lines beginning with series of numbers and periods
  5. /(POST|PUT|DELETE)/ # lines that contain specific HTTP verbs

注意,模式不能捕获特定的组(groups)使它们在代码的ACTIONS部分执行。模式是专门匹配内容的。

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

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

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

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

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

{ ACTIONS }

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

特殊的模式

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

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

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

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

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


  1. # According to the following line
  2. #
  3. # $1 $2 $3
  4. # 00:34:23 GET /foo/bar.html
  5. # _____________ _____________/
  6. # $0
  7.  
  8. # Hack attempt?
  9. /admin.html$/ && $2 == "DELETE" {
  10. print "Hacker Alert!";
  11. }

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

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

行为

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


  1. { print $0; } # prints $0. In this case, equivalent to 'print' alone
  2. { exit; } # ends the program
  3. { next; } # skips to the next line of input
  4. { a=$1; b=$0 } # variable assignment
  5. { c[$1] = $2 } # variable assignment (array)
  6.  
  7. { if (BOOLEAN) { ACTION }
  8. else if (BOOLEAN) { ACTION }
  9. else { ACTION }
  10. }
  11. { for (i=1; i<x; i++) { ACTION } }
  12. { for (item in c) { ACTION } }

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

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

函数

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


  1. { somecall($2) }

这里有一些有限的内置函数可以使用,所以我可以给出这些函数的通用文档(regular documentation)

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


  1. # function arguments are call-by-value
  2. function name(parameter-list) {
  3. ACTIONS; # same actions as usual
  4. }
  5.  
  6. # return is a valid keyword
  7. function add1(val) {
  8. return val+1;
  9. }

特殊变量

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


  1. BEGIN { # Can be modified by the user
  2. FS = ","; # Field Separator
  3. RS = "n"; # Record Separator (lines)
  4. OFS = " "; # Output Field Separator
  5. ORS = "n"; # Output Record Separator (lines)
  6. }
  7. { # Can't be modified by the user
  8. NF # Number of Fields in the current Record (line)
  9. NR # Number of Records seen so far
  10. ARGV / ARGC # Script Arguments
  11. }

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

示例

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

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


  1. =erl_crash_dump:0.3
  2. Tue Nov 18 02:52:44 2014
  3. Slogan: init terminating in do_boot ()
  4. System version: Erlang/OTP 17 [erts-6.2] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]
  5. Compiled: Fri Sep 19 03:23:19 2014
  6. Taints:
  7. Atoms: 12167
  8. =memory
  9. total: 19012936
  10. processes: 4327912
  11. processes_used: 4319928
  12. system: 14685024
  13. atom: 339441
  14. atom_used: 331087
  15. binary: 1367680
  16. code: 8384804
  17. ets: 382552
  18. =hash_table:atom_tab
  19. size: 9643
  20. used: 6949
  21. ...
  22. =allocator:instr
  23. option m: false
  24. option s: false
  25. option t: false
  26. =proc:<0.0.0>
  27. State: Running
  28. Name: init
  29. Spawned as: otp_ring0:start/2
  30. Run queue: 0
  31. Spawned by: []
  32. Started: Tue Nov 18 02:52:35 2014
  33. Message queue length: 0
  34. Number of heap fragments: 0
  35. Heap fragment data: 0
  36. Link list: [<0.3.0>, <0.7.0>, <0.6.0>]
  37. Reductions: 29265
  38. Stack+heap: 1598
  39. OldHeap: 610
  40. Heap unused: 656
  41. OldHeap unused: 468
  42. Memory: 18584
  43. Program counter: 0x00007f42f9566200 (init:boot_loop/2 + 64)
  44. CP: 0x0000000000000000 (invalid)
  45. =proc:<0.3.0>
  46. State: Waiting
  47. ...
  48. =port:#Port<0.0>
  49. Slot: 0
  50. Connected: <0.3.0>
  51. Links: <0.3.0>
  52. Port controls linked-in driver: efile
  53. =port:#Port<0.14>
  54. Slot: 112
  55. Connected: <0.3.0>
  56. ...

产生下面的结果:


  1. $ awk -f queue_fun.awk $PATH_TO_DUMP
  2. MESSAGE QUEUE LENGTH: CURRENT FUNCTION
  3. ======================================
  4. 10641: io:wait_io_mon_reply/2
  5. 12646: io:wait_io_mon_reply/2
  6. 32991: io:wait_io_mon_reply/2
  7. 2183837: io:wait_io_mon_reply/2
  8. 730790: io:wait_io_mon_reply/2
  9. 80194: io:wait_io_mon_reply/2
  10. ...

这是在Erlang进程里运行的函数列表,它们导致了mailboxe变得很庞大。脚本在这:


  1. # Parse Erlang Crash Dumps and correlate mailbox size to the currently running
  2. # function.
  3. #
  4. # Once in the procs section of the dump, all processes are displayed with
  5. # =proc:<0.M.N> followed by a list of their attributes, which include the
  6. # message queue length and the program counter (what code is currently
  7. # executing).
  8. #
  9. # Run as:
  10. #
  11. # $ awk -v threshold=$THRESHOLD -f queue_fun.awk $CRASHDUMP
  12. #
  13. # Where $THRESHOLD is the smallest mailbox you want inspects. Default value
  14. # is 1000.
  15. BEGIN {
  16. if (threshold == "") {
  17. threshold = 1000 # default mailbox size
  18. }
  19. procs = 0 # are we in the =procs entries?
  20. print "MESSAGE QUEUE LENGTH: CURRENT FUNCTION"
  21. print "======================================"
  22. }
  23.  
  24. # Only bother with the =proc: entries. Anything else is useless.
  25. procs == 0 && /^=proc/ { procs = 1 } # entering the =procs entries
  26. procs == 1 && /^=/ && !/^=proc/ { exit 0 } # we're done
  27.  
  28. # Message queue length: 1210
  29. # 1 2 3 4
  30. /^Message queue length: / && $4 >= threshold { flag=1; ct=$4 }
  31. /^Message queue length: / && $4 < threshold { flag=0 }
  32.  
  33. # Program counter: 0x00007f5fb8cb2238 (io:wait_io_mon_reply/2 + 56)
  34. # 1 2 3 4 5 6
  35. flag == 1 && /^Program counter: / { print ct ":", substr($4,2) }

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

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

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

时间: 2024-11-18 00:30:16

20分钟 Awk 入门的相关文章

20分钟MySQL基础入门_Mysql

开始使用 MySQL 为关系型数据库(Relational Database Management System),一个关系型数据库由一个或数个表格组成, 如图所示的一个表格: 表头(header): 每一列的名称;列(row): 具有相同数据类型的数据的集合;行(col): 每一行用来描述某个人/物的具体信息;值(value): 行的具体信息, 每个值必须与该列的数据类型相同; 登录MySQL mysql -h 127.0.0.1 -u 用户名 -p mysql -D 所选择的数据库名 -h

21分钟 MySQL 入门教程_Mysql

21分钟 MySQL 入门教程 目录 一.MySQL的相关概念介绍 二.Windows下MySQL的配置 配置步骤 MySQL服务的启动.停止与卸载 三.MySQL脚本的基本组成 四.MySQL中的数据类型 五.使用MySQL数据库 登录到MySQL 创建一个数据库 选择所要操作的数据库 创建数据库表 六.操作MySQL数据库 向表中插入数据 查询表中的数据 更新表中的数据 删除表中的数据 七.创建后的修改 添加列 修改列 删除列 重命名表 删除整张表 删除整个数据库 八.附录 修改 root

20分钟搞懂神经网络BP算法

在学习深度学习过程中,无意间发现一篇介绍BP算法的文章,感觉非常直观,容易理解.这篇文章的最大亮点是:不像其他介绍BP算法的文章,用一堆数据符号和公式来推导.文中通过使用一条具体的样本数据,为我们展示了模型训练中的参数迭代计算过程,为我们理解BP算法提供了很直观的理解视角:其次,作者也给出了使用python来实现BP的算法.只要你了解过传统神经网络结构以及大学微积分的知识,都可以毫不费力的在20分钟内完全理解BP算法.这里整理出来,供大家学习参考.要看原文的同学,直接跳到文末点击原文链接. 在开

Win7系统网络闲置20分钟自动断开怎么解决

  Win7系统网络闲置20分钟自动断开怎么解决?下面小编将为大家介绍win7系统网络闲置20分钟后断网的解决方法. 1.打开Win7系统的电源计划选择高性能; 2.右下角点击"网络",然后打开打开网络和共享中心--更改适配器设置,然后右键"宽带连接"属性就可以了.

php返回相对时间(如:20分钟前,3天前)的方法

 本文实例讲述了php返回相对时间(如:20分钟前,3天前)的方法.分享给大家供大家参考.具体如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function plural($num) { if ($num != 1) return "s"; } function getRelativeTime($date) { $diff = time() - strtotime($date); if ($diff<60

pl sql- 一条查询语句,在plsql执行6秒,但pc编译出来后却跑20分钟。

问题描述 一条查询语句,在plsql执行6秒,但pc编译出来后却跑20分钟. 查询最大流水号,在plsql执行6秒就有结果了,但用pc编译出来的程序却跑不出,至少20分钟,请问可能是什么原因导致的? 语句: select max(seqno) into :iMaxSeqEN from T where entrydate = to_number(to_char(sysdate,'yyyymmdd')) ; 数据库情况:10G,这张表中公有几亿条数据,每天产生几十万条数据.按entrydate建立了

cron表达式:我想表达:1.每天的3:10到6:40每隔20分钟执行一次,怎么写呢

问题描述 cron表达式:我想表达:1.每天的3:10到6:40每隔20分钟执行一次,怎么写呢 cron表达式:我想表达:1.每天的3:10到6:40每隔20分钟执行一次,2.从3:10开始每隔20分钟执行一次,但执行20次,这两个cron表达式该怎么写呢 解决方案 可以设置每天几点几分,不能直接间隔 比如每小时的00,20,40,分钟执行

上云就是这么简单——阿里云10分钟快速入门

如今,云产品可谓是多种多样,纷繁复杂,作为国内云计算领域领头羊的阿里云就有几十种产品和服务,比如ECS.RDS.OSS.SLB.CDN--,一系列东西很容易让人找不到头绪,尤其是刚刚开始接触网站建设的朋友. 因此我们制作了这个教程,让你在10分钟内了解云产品的具体使用方法. 10分钟快速入门:https://yq.aliyun.com/promotion/142 在学习前别忘了领取免费的云产品套餐哦!   通过这个教程,你可以了解到: 1.一站式建站 建站过程非常简单,小型网站只需一台云服务器E

20分钟构建你自己的Linux发行版

你想要构建自己的 Linux 发行版吗?不喜欢市面上现有的Linux发行版?你认为自己可以构建一款更好的发行版?你很幸运.我要在 20 分钟里面介绍如何构建一款发行版. 是的,你没有听错,就是 20 分钟.想知道如何构建吗?请读下去. 你有好多办法来构建自己的自定义发行版.一种办法就是从头开始构建自己的 Linux 发行版.也可以将 Ubuntu 定制工具(Ubuntu Customization Kit)安装到酷似你想要的那种发行版的发行版,然后定制成个性化的发行版(是不是简单得很?)不过有一