awk是一種小巧的程式設計語言及指令行工具。(其名稱得自于它的創始人alfred aho、peter weinberger 和 brian kernighan姓氏的首個字母)。它非常适合伺服器上的日志處理,主要是因為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中可以使用&&(“與”)、||(“或”)、!(“非”)操作符。你幾乎可以在所有類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)。而且名副其實。
使用直覺的例子能更好地解釋域:
# according to the following line
#
# $1 $2 $3
# 00:34:23 get /foo/bar.html
# _____________ _____________/
# $0
# hack attempt?
/admin.html$/ && $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<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:<0.0.0>
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: [<0.3.0>, <0.7.0>, <0.6.0>]
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:<0.3.0>
state: waiting
=port:#port<0.0>
slot: 0
connected: <0.3.0>
links: <0.3.0>
port controls linked-in driver: efile
=port:#port<0.14>
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:<0.m.n> 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 && /^=proc/ { procs = 1 } # entering the =procs entries
procs == 1 && /^=/ && !/^=proc/ { exit 0 } # we're done
# message queue length: 1210
# 1 2 3 4
/^message queue length: / && $4 >= threshold { flag=1; ct=$4 }
/^message queue length: / && $4 < threshold { flag=0 }
# program counter: 0x00007f5fb8cb2238 (io:wait_io_mon_reply/2 + 56)
# 1 2 3 4 5 6
flag == 1 && /^program counter: / { print ct ":", substr($4,2) }
你跟上思路沒?如果跟上了,你已經了解了awk。恭喜!
原文釋出時間:2015-02-09
本文來自雲栖合作夥伴“linux中國”