gawk
调用gawk
使用示例文件cars
,字段用Tab
分隔,整个文件中没有空格:
plym fury 1970 73 2500 chevy malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850 ford thundbd 2003 15 10500 chevy malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 ford taurus 2004 10 1700 toyota rav4 2002 180 750 chevy impata 1985 85 1550 ford explor 2003 25 9500
gawk程序的基本格式,是对指定的模式(pattern),采用对应的操作(action):
pattern {action}
例如以下程序对于所有的模式(/.*/
)都执行打印当前行的操作:
$ gawk '/.*/ {print}' cars plym fury 1970 73 2500 chevy malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850 ford thundbd 2003 15 10500 chevy malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 ford taurus 2004 10 1700 toyota rav4 2002 180 750 chevy impata 1985 85 1550 ford explor 2003 25 9500
gawk默认的模式是所有的模式(/.*/
);默认的操作是打印当前行:
$ gawk '/chevy/' cars # default action is print chevy malibu 1999 60 3000 chevy malibu 2000 50 3500 chevy impata 1985 85 1550 $ gawk '{print}' cars # default patten is all plym fury 1970 73 2500 chevy malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850 ford thundbd 2003 15 10500 chevy malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 ford taurus 2004 10 1700 toyota rav4 2002 180 750 chevy impata 1985 85 1550 ford explor 2003 25 9500
把从程序文件中读取程序
-
-f filename
-
--file filename
在一个可执行文本文件头上直接加上#!/bin/gawk -f
可以直接作为gawk可执行脚本。
基本概念
正则表达式
gawk中正则表达式用斜杠包围。
字段
使用符号$
加字段号来表示字段,
$0
表示整个一行,
$1
开始表示被分隔符隔出来的第几列:
$ gawk '/chevy/ {print $0}' cars chevy malibu 1999 60 3000 chevy malibu 2000 50 3500 chevy impata 1985 85 1550 $ gawk '/chevy/ {print $3, $1}' cars 1999 chevy 2000 chevy 1985 chevy
匹配操作符
-
匹配操作符
~
,测试某个字段或是变量是否匹配特定的模式。 -
不匹配操作符
!~
。
例,在第一列中包含h
:
$ gawk '$1 ~ /h/ {print}' cars chevy malibu 1999 60 3000 chevy malibu 2000 50 3500 honda accord 2001 30 6000 chevy impata 1985 85 1550
例,第一列中以h
开头:
$ gawk '$1 ~ /^h/' cars honda accord 2001 30 6000
格式化输出
print
函数使用逗号分隔输出的每一列:
$ gawk '$2 ~ /^[tm]/ {print $3, $2, "$" $5}' cars 1999 malibu $3000 1965 mustang $10000 2003 thundbd $10500 2000 malibu $3500 2004 taurus $1700 $ gawk '$3 ~ /5$/ {print $3, $1, "$" $5}' cars 1965 ford $10000 1985 bmw $450 1985 chevy $1550
比较操作符
对于比较的数字,如果有双引用就作为字符串比较, 没有双引号就作为数值比较:
$ gawk '$3 == 1985' cars bmw 325i 1985 115 450 chevy impata 1985 85 1550 $ gawk '$5 <= 3000' cars plym fury 1970 73 2500 chevy malibu 1999 60 3000 bmw 325i 1985 115 450 ford taurus 2004 10 1700 toyota rav4 2002 180 750 chevy impata 1985 85 1550 $ gawk '"2000" <= $5 && $5 <= "9000"' cars plym fury 1970 73 2500 chevy malibu 1999 60 3000 chevy malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 toyota rav4 2002 180 750 $ gawk '2000 <= $5 && $5 <= 9000' cars plym fury 1970 73 2500 chevy malibu 1999 60 3000 chevy malibu 2000 50 3500 honda accord 2001 30 6000
范围操作符
模式中可以用逗号作为范围操作(目标文件中的范围):
$ gawk '/volvo/ , /bmw/' cars volvo s80 1998 102 9850 ford thundbd 2003 15 10500 chevy malibu 2000 50 3500 bmw 325i 1985 115 450
范围是贪婪匹配的:
$ gawk '/chevy/ , /ford/' cars chevy malibu 1999 60 3000 ford mustang 1965 45 10000 chevy malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 ford taurus 2004 10 1700 chevy impata 1985 85 1550 ford explor 2003 25 9500
基本控制结构
if条件:
$ gawk '{if ($1 ~ /ply/) $1 = "Plymouth"; if ($1 ~ /chev/) $1 = "Chevrolet"; print}' cars Plymouth fury 1970 73 2500 Chevrolet malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850 ford thundbd 2003 15 10500 Chevrolet malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 ford taurus 2004 10 1700 toyota rav4 2002 180 750 Chevrolet impata 1985 85 1550 ford explor 2003 25 9500
if-else:
$ cat price_range { if ($5 <= 5000) $5 = "inexpensive" else if ( 5000 < $5 && $5 < 10000) $5 = "please ask" else if (10000 <= $5) $5 = "expensive" printf "%-10s %-8s %2d %5d %-12s\n", $1, $2, $3, $4, $5 } $ gawk -f price_range cars plym fury 1970 73 inexpensive chevy malibu 1999 60 inexpensive ford mustang 1965 45 expensive volvo s80 1998 102 please ask ford thundbd 2003 15 expensive chevy malibu 2000 50 inexpensive bmw 325i 1985 115 inexpensive honda accord 2001 30 please ask ford taurus 2004 10 inexpensive toyota rav4 2002 180 inexpensive chevy impata 1985 85 inexpensive ford explor 2003 25 please ask
while语句:
while (condition) {commands}
for语句:
for (init; condition; increment) {commands}
BEGIN 预先操作步骤
BEGIN模式可以定义在处理数据前的操作。
比如打印一个表头:
$ cat pr_header BEGIN {print "Make Model Year Miles Price"} {print} $ gawk -f pr_header cars Make Model Year Miles Price plym fury 1970 73 2500 chevy malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850 ford thundbd 2003 15 10500 chevy malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 ford taurus 2004 10 1700 toyota rav4 2002 180 750 chevy impata 1985 85 1550 ford explor 2003 25 9500 $ cat pr_header2 BEGIN { print "Make Model Year Miles Price" print "-------------------------------------" } {print} $ gawk -f pr_header2 cars Make Model Year Miles Price ------------------------------------- plym fury 1970 73 2500 chevy malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850 ford thundbd 2003 15 10500 chevy malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 ford taurus 2004 10 1700 toyota rav4 2002 180 750 chevy impata 1985 85 1550 ford explor 2003 25 9500
END 后续操作步骤
END是在处理完所有的行以后才执行的操作:
$ gawk 'END {print "table end" }' cars table end
常用函数
length(str) # 当前行长度 int(num) # 文本转数字 index(str1, str2) # `str2`在`str1`中的位置 split(str, arr, del) # 分割字符串为数组 sprintf(fmt, args) # 格式化输出 substr(str, pos, len) # 截取字符串 tolower(str) # 转大写 otupper(str) # 转小写
length函数
按每一行的长度排序:
$ gawk '{print length, $0}' cars | sort -n 21 bmw 325i 1985 115 450 22 plym fury 1970 73 2500 23 volvo s80 1998 102 9850 24 ford explor 2003 25 9500 24 ford taurus 2004 10 1700 24 toyota rav4 2002 180 750 25 chevy impata 1985 85 1550 25 chevy malibu 1999 60 3000 25 chevy malibu 2000 50 3500 25 honda accord 2001 30 6000 26 ford mustang 1965 45 10000 26 ford thundbd 2003 15 10500
常用变量
-
$0
表示一行记录。 -
$n
表示一行被分隔成的第n
列。
NF 当前记录的字段数量
NR记录行号
变量NR记录了行号。
$ gawk 'NR == 1' cars plym fury 1970 73 2500 $ gawk 'NR <= 1' cars plym fury 1970 73 2500 $ gawk 'NR <= 5' cars plym fury 1970 73 2500 chevy malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850 ford thundbd 2003 15 10500
大于24字符的有哪几行:
$ gawk 'length > 24 {print NR}' cars 2 3 5 6 8 11
显示第二到第四行:
$ gawk 'NR == 2 , NR == 4' cars chevy malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850
处理完所有的行以后,统计行数:
$ gawk 'END {print NR, "cars for sale." }' cars 12 cars for sale.
可以对不同的行采取不同的逻辑,比如这个例子中第一行是日期,和其他的行不一样:
$ cat report NR == 1 {print "Report for", $1 ", " $2} NR > 1 {print $5 "\t" $1} $ (date; cat cars) | gawk -f report Report for 2017年01月19日, 15:18:39 2500 plym 3000 chevy 10000 ford 9850 volvo 10500 ford 3500 chevy 450 bmw 6000 honda 1700 ford 750 toyota 1550 chevy 9500 ford
输入记录的分隔符 RS
默认是换行符。
输出记录的分隔符 ORS
默认是换行符。
输入字段的分隔符FS
变量FS
指定了输入的记录用什么符号分隔字段,默认是空白字符。
例如Linux的用户配置是用:
来分隔的:
$ gawk '/qwshan/ {print}' /etc/passwd qwshan:*:1155062:1049089:U-CN1\qwshan,S-1-5-21-3921055674-81672782-3985740813-106486:/home/qwshan:/usr/bin/zsh
所以程序中要先指定FS
为冒号,这样才能正确地分隔字段。
程序中找取所有UID中最大的那个数,那么下一个可用的UID就是现有最大的那个加1:
$ cat find_uid BEGIN { FS = ":"; saveit = 0 } $3 > saveit { saveit = $3 } END { print "Next available UID is " saveit + 1} $ gawk -f find_uid /etc/passwd Next available UID is 1195796
输出字段分隔符的 OFS变量
用制表符来对齐输出的报表,默认是空格符。
例:
$ cat ofs_defo BEGIN {OFS = "\t"} { if ($1 ~ /ply/) $1 = "Plymouth" if ($1 ~ /chev/) $1 = "Chevrolet" print } $ gawk -f ofs_defo cars Plymouth fury 1970 73 2500 Chevrolet malibu 1999 60 3000 ford mustang 1965 45 10000 volvo s80 1998 102 9850 ford thundbd 2003 15 10500 Chevrolet malibu 2000 50 3500 bmw 325i 1985 115 450 honda accord 2001 30 6000 ford taurus 2004 10 1700 toyota rav4 2002 180 750 Chevrolet impata 1985 85 1550 ford explor 2003 25 9500
printf格式化输出
$ cat printf_demo BEGIN { print " Miles" print "Make Model Year (000) price" print "---------------------------------------------" } { if ($1 ~ /ply/) $1 = "Plymouth" if ($1 ~ /chev/) $1 = "Chevrolet" printf "%-10s %-8s %2d %5d $ %8.2f\n", $1, $2, $3, $4, $5 } $ gawk -f printf_demo cars Miles Make Model Year (000) price --------------------------------------------- Plymouth fury 1970 73 $ 2500.00 Chevrolet malibu 1999 60 $ 3000.00 ford mustang 1965 45 $ 10000.00 volvo s80 1998 102 $ 9850.00 ford thundbd 2003 15 $ 10500.00 Chevrolet malibu 2000 50 $ 3500.00 bmw 325i 1985 115 $ 450.00 honda accord 2001 30 $ 6000.00 ford taurus 2004 10 $ 1700.00 toyota rav4 2002 180 $ 750.00 Chevrolet impata 1985 85 $ 1550.00 ford explor 2003 25 $ 9500.00
输出重定向
把不同的品牌导入到不同的文件中:
$ cat redirect_out /chevy/ {print > "chevfile"} /ford/ {print > "fordfile"} END {print "done."} $ gawk -f redirect_out cars done. $ cat chevfile chevy malibu 1999 60 3000 chevy malibu 2000 50 3500 chevy impata 1985 85 1550 $ cat fordfile ford mustang 1965 45 10000 ford thundbd 2003 15 10500 ford taurus 2004 10 1700 ford explor 2003 25 9500
用户声明变量
用户使用变量时,自动声明并初始化变量。
例如,统计车的价格与年限:
$ cat summary BEGIN { yearsum = 0; costsum = 0; newcostsum = 0; newcount = 0; } { yearsum += $3; costsum += $5; } $3 > 2000 {newcostsum += $5; newcount++} END { printf " Average age of cars is %4.1f years.\n", 2017 - (yearsum / NR) printf " Average cost of cars is %7.2f .\n", costsum / NR printf "Average cost of newer cars is %7.2f .\n", newcostsum / newcount }
$ gawk -f summary cars Average age of cars is 24.1 years. Average cost of cars is 4941.67 . Average cost of newer cars is 5690.00 .
数组
gawk中的数组不一定要用数字做下标,还可以用字符串做下标。
例,定义一个数组myarr
,下标是每条记录的第一列:
$ cat mfnuf {myarr[$1]++} END {for (name in myarr) print name, myarr[name]} $ gawk -f mfnuf cars honda 1 bmw 1 volvo 1 ford 4 plym 1 chevy 3 toyota 1
getline 输入控制
getline语句把输入的一行内容存入指定变量。
如果不指定变量名,那么内容会被存到$0
,并尝试拆分字段。
$ echo 'hello world' | gawk 'BEGIN {getline; print $0}' hello world $ echo 'hello world' | gawk 'BEGIN {getline; print $1}' hello $ echo 'hello world' | gawk 'BEGIN {getline; print $2}' world
不指定文件,就从输入流里读:
$ gawk 'BEGIN {getline aa; print aa}' input in colsole input in colsole
如果输入的内容有多行,那么只读取第一行:
$ echo 'aaaaaaaaaaaa' | gawk 'BEGIN {getline aa; print aa}' aaaaaaaaaaaa $ cat alpha aaaaaaaaaaaa bbbbbbbbbbbb cccccccccccc dddddddddddd eeeeeeeeeeee $ gawk 'BEGIN {getline aa; print aa}' alpha aaaaaaaaaaaa $ cat alpha | gawk 'BEGIN {getline aa; print aa}' aaaaaaaaaaaa
getline
独立于gawk的自动读取与$0
,当读到一个变量的值时,
并不修改$0
,
或是$1
$2
。
$ cat getlineexp { print NR, "$0: ", $0 getline aa print NR, "aa: ", aa } $ gawk -f getlineexp < alpha 1 $0: aaaaaaaaaaaa 2 aa: bbbbbbbbbbbb 3 $0: cccccccccccc 4 aa: dddddddddddd 5 $0: eeeeeeeeeeee 5 aa: dddddddddddd
程序getlineexp2
第一段输出的内容是由gawk自动读取的。
第一段对所有b
开头的行进行特殊处理,getline
读到的内容存到变量hold
中,
然后输出"skip this line",然后输出$1
的值。
$1
的内容是gawk自动读入的记录而不是getline读取的。
最后一段程序显示了当前的行号NR
,即使getline读取变量的时候没有修改$0
,
也会增加NR
的值。
$ cat getlineexp2 # print all lines except those read with getline { print "line #", NR, $0 } # if line begins with "b" process it specially /^b/ { # use getline to read the next line into variable named hold getline hold # print value of hold print "skip this line: ", hold # $0 is not affected when getline reads into a variable # $1 still holds previous value print "previous line began with: ", $1 } { print ">>> finished processing line # ", NR print "" } $ gawk -f getlineexp2 < alpha line # 1 aaaaaaaaaaaa >>> finished processing line # 1 line # 2 bbbbbbbbbbbb skip this line: cccccccccccc previous line began with: bbbbbbbbbbbb >>> finished processing line # 3 line # 4 dddddddddddd >>> finished processing line # 4 line # 5 eeeeeeeeeeee >>> finished processing line # 5
协进程:双向I/O
协进程(coprocess)是指另一个并行运行的进程。
gawk通过启动进程的程序名称前加上|&
操作符方式标识协进程。
协进程必须是一个过滤器(标准输入和输出),并在每处理完一行之后清空输出, 不然会导致积累后面的输出。这样协进程通过双向管道连接到gawk程序。
系统自带的tr
程序每读一行之后不会刷新输出,所以这里自己包装一个to_upper
程序
:
#!/bin/bash #set -x while read arg do echo "$arg" | tr '[a-z]' '[A-Z]' done
在程序里建立双向管道:
$ cat getlineexp3 { print $0 |& "./to_upper" "./to_upper" |& getline hold print hold } $ gawk -f getlineexp3 < alpha AAAAAAAAAAAA BBBBBBBBBBBB CCCCCCCCCCCC DDDDDDDDDDDD EEEEEEEEEEEE
从网络读取数据
在协进程的基础上,可以实现从网络读取数据,一般格式为:
/inet/protocol/local-port/remote-host/remote-port
-
protocol
是协议,一般为tcp
或udp
。 -
local-port
默认为0
,也可以指定本地端口。 - 还有远程地址与端口。
BEGIN { # create web client from website server = "/inet/tcp/0/localhost/8080" print "GET /files/datafile/cars" |& server while (server |& getline) { print $0 } }
其他例子
分类分组
John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA
需要对上述文件的操作:
- 将上述文件中的最后一列中的MA、VA、PA等替换成城市名称。
- 根据城市名称进行归类,将同属一个城市的人归为同一类。
操作(1) 创建sed脚本完成替换任务。
s/CA/, California/ s/MA/, Massachusetts/ s/OK/, Oklahoma/ s/PA/, Pennsylvania/ s/VA/, Virginia/
(2) 创建awk脚本对城市进行排序并根据城市对人进行归类。
#!/bin/sh awk -F, '{ print $4 ", " $0 }' $* | # $*代表接收命令行中的所有字符流,将城市 $4 一列单独取出放在开头方便排序。 sort | # 排序 awk -F, ' $1 == LastState { print " \t" $2 # 如果此行城市名与LastState相同,则只打印人名 } $1 != LastState { # 如果此行城市名与LastState不同,将此行的城市名赋给LastState,首先顶格打印城市名,然后再打印城市名。 LastState = $1 print $1 print " \t" $2 }'
(3) 执行下述命令完成文件处理。
sed -f nameState.sed list|sh byState.awk
(4) 即可完成如下结果。
California Amy Wilde Massachusetts Eric Adams John Daggett Sal Carpenter Oklahoma Orville Thomas Pennsylvania Terry Kalkas Virginia Alice Ford Hubert Sims
通过IP地址定位进程
通过IP地址定位竞争对手先看下面这两行代码:
netstat -anp | grep 185.71.65.238 | awk '{print $7}' | awk -F'[/]' '{print $1}' | xargs -I % kill -9 % netstat -anp | grep 140.82.52.87 | awk '{print $7}' | awk -F'[/]' '{print $1}' | xargs -I % kill -9 %
其中的含义是:在受害者机器里寻找特定IP地址 (和竞争对手有关,比如竞争对手的矿池地址)相关的进程,杀掉它们。
通过端口号定位进程
通过一些「奇怪的端口号」定位竞争对手再看这几行代码:
netstat -anp | grep :23 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % netstat -anp | grep :443 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % netstat -anp | grep :143 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % netstat -anp | grep :2222 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % netstat -anp | grep :3333 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % netstat -anp | grep :3389 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % netstat -anp | grep :5555 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % netstat -anp | grep :6666 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % netstat -anp | grep :6665 | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 %
像2222、3333、6666、5555、6665这种端口号,一般情况下是不会开的, 但是挖矿木马经常用,所以挖矿团伙会把竞争对手们常用的端口号收集起来, 在受害者的机机器里寻找相关进程,咔嚓掉。