awk命令简介(二)

文章的干货来源于酷壳的**AWK 简明教程以及Linux与unix shell编程指南**。

本文纯属搬砖。

最近经常遇到需要处理大段文本输出的情况,所以想学习一下awk命令,这个命令更像是一个微型语言。

《awk命令简介(二)》着重介绍awk命令的详情,内容主要来自《Linux与unix shell编程指南》

书接上回。

awk 命令详解

调用awk

如果在命令行中使用awk,通常型为:

1
$ awk -F field-separator 'commands' input-files

-F field-separator为可选项,用于指定域分隔符,默认为空格,如果需要处理如','':'';'之类的字符做分隔符时,可以设置该值,如使用':'的情形:

1
$ awk -F: 'commands' input-files

也可以将awk命令写在文件中,作为脚本调用,如调用名为awk-script-file的脚本文件:

1
$ awk -f awk-script-file input-files

-f选项用于指定脚本文件名。

awk脚本

模式和动作

awk脚本由各种动作模式组成。

awk使用-F命令指定的分隔符(不指定时使用空格)分离记录中的域,直到发现新一行。这个动作将一直持续到文件结束。

awk命令可以有许多语句,语句中的模式用于控制动作的触发条件。

模式可以是任何条件语句、复合语句或正则表达式。模式包括了两个特殊字段,BEGINENDBEGIN语句使用在awk的任何文本浏览动作之前,之后awk文本浏览动作依据输入文件开始执行,END语句用来用来在awk完成文本浏览动作后打印输出文本总数及结尾状态标识。

动作{}内声明,通常为打印动作,或是诸如条件语句或循环语句。如果不指名动作,awk将打印所有浏览记录。

记录和域

awk会将读取的一条记录用-F指定的分隔符分为多个域,并依次将域命名为$1, $2, $3等,这些域名的使用类似于语言中变量名的使用。

{print $1, $3}就表示打印第1域和第3域,而$0表示所有的域,所以{print $0}表示打印所有域。

上面的在{}中的print就是一个awk动作。

假设有数据文件grade.txt(此文件会在本文的示例中大量出现),文件分隔符为TAB(即'\t'):

1
2
3
4
5
6
$ cat grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Transly 05/99 4712 Brown-2 12 30 28

常用的输入输出

使用命令行时通常我们将结果重定向到文件中:

1
$ awk -F\t '{print $1, $3}' grade.txt > wow

或是使用tee,在将结果输出至标准输出的同时,写入文件:

1
$ awk -F\t '{print $1, $3}' grade.txt | tee wow

如果是脚本,在输入上我们可以使用文件:

1
$ belt.awk grade.txt

或是使用标准输入:

1
$ belt.awk < grade.txt

或是管道:

1
$ grade.txt | belt.awk

打印头尾信息

  1. 使用BEGIN打印表头,为了对齐使用了制表符\t

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ awk 'BEGIN {print "Name\tBelt\n--------------------"}
    quote> {print $1"\t"$4}' grade.txt
    Name Belt
    --------------------
    M.Tansley Green
    J.Lulu green
    P.Bunny Yellow
    J.Troll Brown-3
    L.Transly Brown-2
  2. 使用END打印表尾:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ awk 'BEGIN {print "Name\n----------"} {print $1} END {print "----------\ntotle: " NR}' grade.txt
    Name
    ----------
    M.Tansley
    J.Lulu
    P.Bunny
    J.Troll
    L.Transly
    ----------
    totle: 5

条件操作

操作符 描述
< 小于
<= 小于等于
== 等于
> 大于
>= 大于等于
~ 匹配正则表达式
!~ 不匹配正则表达式
  1. 使用正则表达式匹配域时,用~/Regular_Express/,如果使用if语句则需要放在()中。示例,为第4域匹配正则表达式,输出匹配的记录:
1
2
3
$ awk '{if($4~/Brown/) print $0}' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Transly 05/99 4712 Brown-2 12 30 28

上例中不使用if也可以,不指定动作时awk默认输出整条记录:

1
2
3
$ awk '$4~/Brown/' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Transly 05/99 4712 Brown-2 12 30 28
  1. 使用正则表达式模糊匹配域:
1
2
3
4
5
$ awk '{if($3~/48/) print $0}' grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26

1
2
3
4
5
$ awk '$3~/48/ {print $0}' grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
  1. 使用==精确匹配:
1
2
$ awk '$3==48 {print $0}' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28

1
2
$ awk '{if($3==48) print $0}' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28
  1. 使用!~的正则表达式:
1
2
3
4
$ awk '$0 !~ /Brown/' grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28

配合if使用!~

1
2
3
4
$ awk '{if($4!~/Brown/) print $1, $4}' grade.txt
M.Tansley Green
J.Lulu green
P.Bunny Yellow
  1. 比较:
  • 小于:
1
2
3
$ awk '{if($6<$7) print $1" try better at next comp"}' grade.txt
M.Tansley try better at next comp
J.Lulu try better at next comp
  • 小于等于:
1
2
3
4
$ awk '{if($6<=$7) print $1}' grade.txt
M.Tansley
J.Lulu
J.Troll
  • 大于:
1
2
3
$ awk '{if($6>$7) print $1}' grade.txt
P.Bunny
L.Transly
  1. 常见的正则表达式功能:
  • 匹配大小写,使用[]匹配字符:
1
2
3
$ awk '/[Gg]reen/' grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
  • 通配符,.
1
2
3
$ awk '$1 ~ /^....a/' grade.txt
M.Tansley 05/99 48311 Green 8 40 44
L.Transly 05/99 4712 Brown-2 12 30 28
  • 逻辑或,|
1
2
3
4
$ awk '$4 ~ /(Yellow|Brown)/' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Transly 05/99 4712 Brown-2 12 30 28
  • 行首^
1
2
3
$ awk '/^J/' grade.txt
J.Lulu 06/99 48317 green 9 24 26
J.Troll 07/99 4842 Brown-3 12 26 26
  • 行尾$:
1
2
3
$ awk '/28$/' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28
L.Transly 05/99 4712 Brown-2 12 30 28

复合逻辑

  1. 逻辑与,&&

    1
    2
    $ awk '{if ($1=="P.Bunny" && $4=="Yellow") print $0}' grade.txt
    P.Bunny 02/99 48 Yellow 12 35 28
  2. 逻辑或,||

    1
    2
    3
    4
    $ awk '{if ($4=="Yellow" || $4~/Brown/) print $0}' grade.txt
    P.Bunny 02/99 48 Yellow 12 35 28
    J.Troll 07/99 4842 Brown-3 12 26 26
    L.Transly 05/99 4712 Brown-2 12 30 28
  3. 逻辑否,!

    1
    2
    3
    4
    5
    6
    $ awk '$4 != "Brown" {print $1, $4}' grade.txt
    M.Tansley Green
    J.Lulu green
    P.Bunny Yellow
    J.Troll Brown-3
    L.Transly Brown-2

内置变量的示例

  1. 已读取的记录数,NR
1
2
3
4
5
6
7
$ awk '{print} END {print "total: "NR}' grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Transly 05/99 4712 Brown-2 12 30 28
total: 5
  • 使用NR检查保证文件记录数大于0:
1
2
3
$ awk '{if (NR>0 && $4~/Brown/) print $0}' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Transly 05/99 4712 Brown-2 12 30 28
  1. 当前记录的域数,NF
1
2
3
4
5
6
7
$ awk '{print NF, NR, $0} END {print FILENAME}' grade.txt
7 1 M.Tansley 05/99 48311 Green 8 40 44
7 2 J.Lulu 06/99 48317 green 9 24 26
7 3 P.Bunny 02/99 48 Yellow 12 35 28
7 4 J.Troll 07/99 4842 Brown-3 12 26 26
7 5 L.Transly 05/99 4712 Brown-2 12 30 28
grade.txt
  • 显示当前目录名:
1
2
3
4
$ pwd
/Users/zealot
$ echo $PWD | awk -F/ '{print $NF}'
zealot
  • 显示文件名:
1
2
$ echo "/Users/zealot/grade.txt" | awk -F/ '{print $NF}'
grade.txt

awk操作符

在awk中使用操作符,基本表达式可以划分为数字型、字符串型、变量型、域及数组元素。

操作符 描述
= += *= /= %= ^= 赋值操作符
? 条件表达操作符
&& ! 与、或、非操作符
~ !~ 匹配操作符,包括匹配和不匹配
< <= == != >> 关系操作符
+ - * / % ^ 算术操作符
++ -- 前缀和后缀
  1. 设置输入域到域变量名:

    给特定的数据域设置有意义的域名,可以使得代码更加清楚明了。

    一般的变量名设置方式为name=$n,如在grade.txt的例子中,可以设置name=$1;belts=$4

    1
    2
    $ awk '{name=$1;belts=$4; if(belts~/Yellow/) print name " is belt " belts}' grade.txt
    P.Bunny is belt Yellow
  2. 域值的比较操作:

有两种方式测试一数值域是否小于另一数值域:

  1. 在关系操作中使用实际数值;
  2. BEGIN中给变量名赋值;
    关系操作必须写在圆括号内,如查询比赛中得分在27以下的学生:
1
2
3
$ awk '{if($6<27) print $0}' grade.txt
J.Lulu 06/99 48317 green 9 24 26
J.Troll 07/99 4842 Brown-3 12 26 26

BEGIN中给变量名赋值可以使代码更清晰:

1
2
3
$ awk 'BEGIN {BASELINE=27} {if($6<BASELINE) print $0}' grade.txt
J.Lulu 06/99 48317 green 9 24 26
J.Troll 07/99 4842 Brown-3 12 26 26
  1. 修改数值域取值:

需要注意的是,当在awk中修改任何域时,实际输入文件是不会被修改的,修改的只是 保存在缓存里的awk复本。

1
2
3
4
5
6
$ awk '{if($1=="M.Tansley") $6=$6-1; print NR, NF $1, $6, $7}' grade.txt
M.Tansley 39 44
J.Lulu 24 26
P.Bunny 35 28
J.Troll 26 26
L.Transly 30 28
  1. 修改文本域:
1
2
3
4
5
$ awk '{if($1=="J.Troll") ($1="J.L.Troll"); print $1}' grade.txt
M.Tansley
J.Lulu
P.Bunny
J.L.Troll

赋值表达式通常放在括号中,以增强代码的可读性,非必须,如上一项中的$6=$6-1

  1. 只显示修改记录:
1
2
$ awk '{if($1=="J.Troll") {$1="J.L.Troll"; print $1}}' grade.txt
J.L.Troll

此处使用if语句取得需要修改的位置,而后再使用赋值动作$1="J.L.Troll"并打印。

  1. 创建新的输出域:
1
2
3
4
$ awk 'BEGIN {print "Name\tDifference"} {if($6<$7) {$8=$7-$6; print $1, $8}}' grade.txt
Name Difference
M.Tansley 4
J.Lulu 2

或是直接将计算结果赋给变量名并打印:

1
2
3
$ awk 'BEGIN {print "Name\tDifference"} {if($6<$7) {diff=$7-$6; print $1, diff}}' grade.txt
Name Difference
M.Tansley 4
  1. 列值累加:
1
2
3
4
5
6
7
$ awk 'total+=$6; END{print "Club student total points: " total}' grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Transly 05/99 4712 Brown-2 12 30 28
Club student total points: 155

需要注意的是,不必在awk中显式说明打印所有记录,当每一个操作匹配时,打印是缺省动作。

如果不想这种缺省打印动作出现,可以显式声明不带打印命令的动作:

1
2
$ awk '{total+=$6} END{print "Club student total points: " total}' grade.txt
Club student total points: 155

累加动作并不一定要写在圆括号中,但是这样做可以增强代码可读性。

  • 另一个累加列的示例,统计当前目录中文件的总大小:
1
2
3
4
ls -lAG | awk '$1~/^[^d]/ {print $9"\t"$5} {total+=$5} END {print "total " total/1024 " KB"}'
...file name followed by size...
grade.txt 176
total 234.544 KB

大概过程是,先把当前目录中的文件信息列表用管道送给awk,而后使用第一个域滤掉目录项(d开头的项目,其实直接用/^[^d]/匹配整行就可以了),再累加第五列,最后在打印处理成以KB为单位的结果。

内置字符串函数

函数 描述
gsub(r,s) 在整个$0中用s替代r
gsub(r,s,t) 在整个t中用s替代r
index(s,t) 返回s中字符串t的第一位置
length(s) 返回s长度
match(s,r) 测试s是否包含匹配r的字符串
split(s,a,fs) 在fs上将s分成序列a
sprint(fmt,exp) 返回经fmt格式化后的exp
sub(r,s) 用$0中最左边最长的子串代替s
substr(s,p) 返回字符串s中从p开始的后缀部分
substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分
  1. gsub(r,s), gsub(r,s,t),在整个个记录中匹配字符串模式r,并用s进行替换。若不指定t,gsub函数默认在$0上替换:
1
2
$ awk 'gsub(/4842/, 4899) {print $0} ' grade.txt
J.Troll 07/99 4899 Brown-3 12 26 26
  1. index(s,t),返回字符串s中t第一次出现的位置:
1
2
$ awk 'BEGIN {print index("Bunny", "ny")}' grade.txt
4
  1. length(s),返回字符串s的长度:
1
2
$ awk '$1=="J.Troll" {print length($1) " " $1}' grade.txt
7 J.Troll

或是直接传入字符串:

1
2
$ awk 'BEGIN {print length("Hello World~")}'
12
  1. match(s,r),测试目标串s是否包含匹配模式r的字符串,若包含,则返回r第一次出现的位置,若不包含则返回0:
1
2
3
4
5
6
$ awk 'BEGIN {print match("ABCD", /d/)}'
0
$ awk 'BEGIN {print match("ABCD", /C/)}'
3
$ awk '$1=="J.Lulu" {print match($1, "u")}' grade.txt
4
  1. `split(s,a,fs),使用分隔符fs将字符串s切割,存入数组a中,并返回切割后数组元素个数:
1
2
$ awk 'BEGIN {print split("123#456#789", myarray, "#"), myarray[1], myarray[2], myarray[3]}'
3 123 456 789
  1. sub(r,s,t),在t中查找模式r,将第一次匹配到的字符串替换为s:
1
2
3
4
5
6
$ awk '$1=="J.Troll" {sub(/26/, 29, $0)} {print $0}' grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 29 26
L.Transly 05/99 4712 Brown-2 12 30 28
  1. substr(s,p), substr(s,p,n),在字符串s中截取从p开始到字符串结束的后缀并返回,若指定n,则截取从p开始长度为n的字符串并返回:
1
2
3
4
5
6
7
8
9
10
11
12
$ awk '$1=="J.Troll" {print substr($0, 3)}' grade.txt
Troll 07/99 4842 Brown-3 12 26 26
$ awk '$1=="J.Troll" {print substr($0, 3, 5)}' grade.txt
Troll
$ awk '{print substr($1, 3)}' grade.txt
Tansley
Lulu
Bunny
Troll
Transly
$ awk 'BEGIN {STR="hello world~"} END {print substr(STR,7)}' grade.txt
world~
  • 另:通过管道使用awk函数的例子:
1
2
3
4
5
echo "stand-by" | awk '{print length($0)}'
8
$ STR="mydoc.txt"
$ echo $STR | awk '{print substr($0,1,5)}'
mydoc

awk命令的printf函数

printf的基本用法和C语言类似,形式为printf format, values

awk printf修饰符 描述
  •               |左对齐
    
    width |域的步长,用0表示0步长
    .prec |最大字符串长度,或小数点右边的位数(精确度)
awk printf格式控制符 描述
%c ASCII字符
%d 整型
%e 浮点数,科学计数法
%f 浮点数
%g 浮点数,由awk决定使用%e%f
%o 八进制数
%s 字符串
%x 十六进制数
  1. 文字转换:
1
2
3
4
$ echo 65 | awk '{printf "%c\n", $0}'
A
$ awk 'BEGIN {printf "%c\n", 65}'
A
  1. 格式化输出:

第一列为行号,步长3字符,左对齐;第二列为姓名,步长15字符,左对齐;第四列为序列号,后接换行:

1
2
3
4
5
6
$ awk '{printf "%-3d%-15s%s\n", NR,$1,$3}' grade.txt
1 M.Tansley 48311
2 J.Lulu 48317
3 P.Bunny 48
4 J.Troll 4842
5 L.Transly 4712

或加上表头:

1
2
3
4
5
6
7
8
$ awk 'BEGIN{printf "%-3s%-15s%s\n---------------------------\n", "NR","Name","No."} {printf "%-3d%-15s%s\n", NR,$1,$3}' grade.txt
NR Name No.
---------------------------
1 M.Tansley 48311
2 J.Lulu 48317
3 P.Bunny 48
4 J.Troll 4842
5 L.Transly 4712
  1. 向一行awk命令传参数:
1
2
$ awk '{if($5<AGE) print $0}' AGE=10 grade.txt
M.Tansley 05/99 48311 Green 8 40 44
  1. awk脚本文件:

将awk写入文件中执行,就不必每次输入大段命令了,而且可以好的排版和注释,来提高代码的可读性。

  • 场景1,将上面的命令写入脚本文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/awk -f
# all comment line must start with a hash `#`
# name: student_total.awk
# to call: student_total.awk grade.txt
# print total and average of club student points

# print a header first:
BEGIN{
print "Student Date Member No. Grade Age Points Max"
print "Name Joined Gained Point Available"
print "================================================================"
}

# let's add the scores of points gained
(total+=$6)

# finished processing, now let's print the total and average points:
END{
print "Club students total points: " total
print "Average club students points: " total/NR
}

接下来使用chmod给脚本加上“所有用户可执行”的权限,再使用cp将脚本复制到shell可以找到的地方:

1
2
$ chmod a+x student_total.awk
$ sudo cp student_total.awk /usr/bin/

然后执行:

1
2
3
4
5
6
7
8
9
10
11
$ student_total.awk grade.txt
Student Date Member No. Grade Age Points Max
Name Joined Gained Point Available
================================================================
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Transly 05/99 4712 Brown-2 12 30 28
Club students total points: 155
Average club students points: 31

顺便提一下,在OSX上使用ls命令时,会发现有的目录或文件的权限位的最后附有一个@+符号,其中@说明该目录或文件具有extended attributes,而+符合说明该目录或文件具有类似ACL的非标准权访问限控制策略。可以通过在ls后添加-@命令查看。详情见“ls” on mac and extended attributes

  • 场景2,清除重复输出:

当调试程序时,遇到这样一个日志文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat error.log
INVALID LCSD 98GJ23
ERROR*
ERROR*
CAUTION LPSS ERROR ON ACC NO.
ERROR*
ERROR*
ERROR*
ERROR*
ERROR*
PASS FIELD INVALID ON LDPS
ERROR*
ERROR*
PASS FIELD INVALID ON GHSI
ERROR*
CAUTION LPSS ERROR ON ACC NO.
ERROR*
ERROR*

我们想将其中的重复的多行ERROR*合并为一行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/awk -f
# name: error_strip.awk
# to call: error_strip.awk <filename>
# strips out the ERROR* lines if there are more than one
# ERROR* lines after each failed record.

BEGIN{error_line=""}

# tell awk who is "ERROR*"
{
if($0=="ERROR*" && error_line=="ERROR*")
# goto next line
next;
error_line=$0;
print
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
$ error_strip.awk error.log
INVALID LCSD 98GJ23
ERROR*
CAUTION LPSS ERROR ON ACC NO.
ERROR*
PASS FIELD INVALID ON LDPS
ERROR*
PASS FIELD INVALID ON GHSI
ERROR*
CAUTION LPSS ERROR ON ACC NO.
ERROR*
  1. 在脚本中制定分隔符FS:

再复习一下,在shell中使用awk命令时,用-F指定分隔符:

1
$ awk -F: '{print $0}' input-file

而在脚本中,则是设置FS变量,值得注意的都是,FS变量需要放在BEGIN部分:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/awk -f
# to call: passwd.awk /private/etc/passwd
# print out the first and fifth fields

BEGIN{FS=":"}

{
# pass the comments of the file
if($0 ~ /^#/)
next;
print $1"\t"$5
}

执行结果:

1
2
3
4
nobody	Unprivileged User
root System Administrator
daemon System Services
...
  1. 向脚本传递参数:
  • 前面提到,在shell中使用awk命令时传递参数的形式是:
1
$ awk '{if($5<AGE) print $0}' AGE=10 grade.txt
  • 而使用脚本时,传递的方式的形式也基本相同:
1
awk script_file var=value input_file

示例:

1
2
3
4
5
6
7
8
9
#!/usr/bin/awk -f
# check on how many fields in a file
# name: field_check.awk
# to call: field_check.awk MAX=n FS=<separator> input-file

NF!=MAX
{
print("line " NR " does not have " MAX " fields")
}

调用时命令为field_check.awk MAX=7 FS=":" /private/etc/passwd

  • 将上面提到的传递“AGE”参数的例子改写,就应为:
1
2
3
4
5
6
7
#!/usr/bin/awk -f
# name: age.awk
# to call: age.awk AGE=10 grade.txt
# print students whose age are lower than age supplied on the command line
{
if($5<AGE) print $0
}

执行:

1
2
3
$ age.awk AGE=10 grade.txt
M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
  • 通过管道使用脚本:

du命令获得数据后处理输出:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/awk -f
# name: du.awk
# to call: du | du.awk
# print file/direc's in bytes and blocks

BEGIN{
OFS="\t";
print "name", "\t\t", "bytes", "blocks\n";
print "=================================="
}

{print $2, "\t\t", $1*512, $1}

执行:

1
2
3
4
5
6
$ du | du.awk
name bytes blocks

==================================
. 20461768704 39964392
...
  1. awk数组:

上面也提到了awk数组,现在看看更多关于数组的应用。

数组在使用前,不需要声明,也不需要设定元素个数,我们经常会在循环中使用到数组,基本语法如下:

1
For (element in array) print array(element)
  • 如果把上面的例子在脚本中重新实现:
1
2
3
4
5
6
7
8
9
10
#!/usr/bin/awk -f
# name: array_test.awk
# prints out an array
BEGIN{
recode="123#456#789";
split(recode, myarray, "#");
}
END{
for(i in myarray) print i, myarray[i]
}

这时我们可以使用/dev/null作为输入文件:

1
2
3
4
$ array_test.awk /dev/null
2 456
3 789
1 123
  • 一个稍微复杂的统计示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cat grade_student.txt
Yellow#Junior
Orange#Senior
Yellow#Junior
Purple#Junior
Brown-2#Junior
White#Senior
Orange#Senior
Red#Junior
Brown-2#Senior
Yellow#Senior
Red#Junior
Blue#Senior
Green#Senior
Purple#Junior
White#Junior

要统计文件中黄、橙、红带各有多少种,分别有多少成年人和未成年人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/awk -f
# name: belts.awk
# to call: belts.awk grade_student.txt
# loop through the file and count how many belts we have
# in (yellow, orange, red) also count how many adults and
# juniors we have.
#
# start from BEGIN
# set FS and load the arrays with our values
BEGIN{
FS="#"
# load the belt colors we are interested in only
belt["Yellow"]
belt["Orange"]
belt["Red"]
# load the type of students
student["Junior"]
student["Senior"]
}
# loop through array that holds the belt colors against field-1
# if we have a match, keep a running total
{
for(color in belt)
if($1==color)
belt[color]++
}
# loop through array that holds the student type against field-2
# if we have a match, keep a running total
{
for(type in student)
if($2==type)
student[type]++
}
# finish processing, print out the match for each array
END{
for(color in belt)
print "The club has", belt[color], color, "belts"
for(type in student)
print "The club has", student[type], type, "students"
}

执行:

1
2
3
4
5
6
$ belts.awk grade_student.txt
The club has 2 Orange belts
The club has 2 Red belts
The club has 3 Yellow belts
The club has 8 Junior students
The club has 7 Senior students

小结

awk是shell中的一个重要文本处理工具,在特定情况下可以方便快捷的配合其他命令输出有格式要求的文本并统计,就像像黑客一样使用Linux命令行:我喜欢使用命令行的原因中提到的这样~

-EOF-

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×