awk命令详解(二)

第一篇的链接:Linux awk命令总结(一)

1. 处理数组

为了在单个变量中存储多个值,许多编程语言都提供了数组,在awk中使用关联数组提供数组的功能。

关联数组类似于散列表和字典,索引值可以是任意的文本字符串,对索引的唯一要求是每个索引字符串都能够唯一的对应赋值给它的数据元素。

(1)定义数组变量

可以使用标准的赋值语句来定义数组变量,格式如下:

array[index] = element

其中array是变量名,index是索引值,element是数据元素的值。

$ awk 'BEGIN{
> nums["a"] = "abc"
> nums["b"] = "def"
> print nums["a"], nums["b"]
> }'
abc def

(2)遍历数组变量

如果要在awk中遍历一个关联数组,可以使用for语句的一种特殊形式(for each):

for (var in array) {

    statements

}

举例如下:

$ awk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> var["m"] = 3
> var["u"] = 4
> for (element in var) {
>     print "Index:", element, " - Value:", var[element]
> }
> }'
Index: g  - Value: 2
Index: m  - Value: 3
Index: u  - Value: 4
Index: a  - Value: 1

需要注意的是,索引值不会按照任何特定的顺序返回,但它们都能够指向对应的数据元素值。

(3)删除数组变量

从关联数组中删除数组索引需要一个特殊的命令:

delete array[index]

删除命令会从数组中删除索引值和与其对应的数据元素值,一旦从关联数组中删除了索引值,就无法再用它来提取数据元素值:

$ awk 'BEGIN {
> var["a"] = 1
> var["g"] = 2
> for (element in var)
> {
>     print "Index:", element, " - Value:", var[element]
> }
> delete var["g"]
> print "-----"
> for (element in var)
> {
>     print "Index:", element, " - Value:", var[element]
> }
> }'
Index: g  - Value: 2
Index: a  - Value: 1
-----
Index: a  - Value: 1

2. 使用模式

awk程序支持多种类型的匹配模式类过滤数据,这一点与sed编辑器类似。BEGIN和END关键字是用来在读取数据流之前和之后执行命令的特殊模式,类似地,你可以创建其他模式,在数据流中出现匹配数据时执行一些命令。

(1)正则表达式

在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号之前

$ cat test2.txt
data11,data12,data13
data21,data22,data23
data31,data32,data33
$ awk 'BEGIN {FS=","} /11/{print $1}' test2.txt
data11
$ awk 'BEGIN {FS=","} /11/{print $2}' test2.txt
data12

第一个例子很好理解,但是在第二个例子中匹配的是11为什么打印出来的是data12呢?这是因为正则表达式匹配是对输入记录进行匹配的(变量RS规定的输入记录),在默认是按行匹配的情况下(RS的默认值为换行符\n),数据中的第一行含有11,因此第一行的第二个数据字段data12打印了出来。显然这不是我们想要的结果,如果我们需要用正则表达式匹配某个特定的数据字段,应该使用匹配操作符。

(2)匹配操作符

匹配操作符(matching operator)允许将正则表达式限定在数据中的特定数据字段。匹配操作符是波浪线(~)。可以指定匹配操作符、数据字段变量以及要匹配的正则表达式。

对上面的例子稍作修改,我们想在test2.txt中匹配含有11的第二个数据字段,可以写做:

$ awk 'BEGIN {FS=","} $2 ~ /11/{print $2}' test2.txt
$

没有任何输出,说明没有匹配到含有11的第二个数据字段,这正式我们想要的结果。

也可以用!符号来排除正则表达式的匹配:

$ awk 'BEGIN {FS=","} $3 !~ /13/{print $3}' test2.txt
data23
data33

3. 数学表达式

除了正则表达式,你也可以在匹配模式中使用数学表达式。这个功能在匹配数据字段中的数值时非常方便。举个例子,如果你想显示所有属于root用户组(组ID为0)的系统用户,可以用这个脚本:

$ awk 'BEGIN {FS=":"} $4 == 0 {print $1}' /etc/passwd
root

这段脚本会查看第四个数据字段含有值0的记录。

可以使用任何常见的数学比较表达式:

  • x == y
  • x <= y
  • x < y
  • x >= y
  • x > y

也可以对文本数据使用表达式,但必须要小心,跟正则表达式不同,表达式必须完全匹配。

$ awk -F, '$1 == "data"{print $1}' test2.txt
$
$ awk -F, '$1 == "data11"{print $1}' test2.txt
data11

4. 结构化命令

(1)if语句

awk支持标准的if-else格式的if语句。你必须为if语句定义一个求值的条件,并将其用圆括号括起来。可以写在不同行,也可以写在同一行。下面是一个例子:

$ cat test5.txt
10
5
13
50
34
$ awk '{
> if ($1 > 20)
> {
>     x = $1 * 2
>     print x
> } else
> {
>     x = $1 / 2
>     print x
> }}' test5.txt
5
2.5
6.5
100
68

也可以在单行上使用else子句,但必须在if语句部分之后使用分号:

$ awk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' test5.txt
5
2.5
6.5
100
68

(2)while语句

awk支持标准的while语句:

while (condition) {

    statements

}

此外还支持在while循环中使用break语句和continue语句从循环中跳出或是跳过一个循环:

$ cat test6.txt
130 120 135
160 113 140
145 170 215
$ awk '{
> total = 0
> i = 1
> while (i < 4)
> {
>     total += $i
>     if (i == 2)
>         break
>     i++
> }
> avg = total / 2
> print "The average of the first two data elements is:", avg
> }' test6.txt
The average of the first two data elements is: 125
The average of the first two data elements is: 136.5
The average of the first two data elements is: 157.5

break语句用来在i的值为2时从while循环中跳出。

除此之外,awk还支持do-while语句,它类似于while语句,但会在检查条件语句之前执行命令,下面时do-while的格式:

do {

    statements

} while (condition)

(3)for语句

awk支持标准的C风格for循环:

$ awk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
>     total += $i
> }
> avg = total / 3
> print "Average:", avg
> }' test6.txt
Average: 128.333
Average: 137.667
Average: 176.667

5. 格式化打印

print语句在显示数据上并没有提供多少控制,你能做的知识控制输出字段的分隔符(OFS)。如果要创建详尽的报告,通常都要为数据选择特定的格式和位置。使用printf命令能够实现这一效果。awk中的printf和C语言中的printf是一样的。下面是printf命令的格式:

printf "format string", var1, var2, ......

format string是格式化输出的关键,它会用文本元素和格式化指定符来具体制定如何呈现格式化输出。格式化指定符是一种特殊的代码,会指明显示什么类型的变量以及如何显示。awk程序会将每个格式化指定符作为占位符,供命令中的变量使用,第一个格式化指定符对应第一个变量,第二个对应第二个变量,以此类推。

格式化指定符采用如下形式:

%[modifier]control-letter

其中control-letter是一个单字符代码,用于指明显示什么类型的数据,而modifier则定义了可选的格式化特性,下面列出了可用在格式化指定符中的控制字母:

控制字母 描述
c 将一个数作为ASCII字符显示
d 显示一个整数值
i 显示一个整数值(和d一样)
e

用科学计数法显示一个数

f 显示一个浮点值
g 用科学计数法或浮点数显示(选择一个较短的格式)
o 显示一个八进制值
s 显示一个文本字符串
x 显示一个十六进制值
X 显示一个十六进制值,但用大写字母A~F

因此,如果你需要显示一个字符串变量,可以使用%s,如果你需要显示一个整数变量,可以用%d或%i。如果你想要用科学计数法显示很大的值,就用%e。

$ awk 'BEGIN {x = 10 * 100; printf "The answer is: %e\n", x}'
The answer is: 1.000000e+03

除了控制字母外,还有三种修饰符可以用来进一步控制输出:

  • width:指定了输出字段最小宽度的数字值。如果输出短于这个值,printf会将文本右对齐,并用空格进行填充。如果输出比指定的宽度还要长,则按照实际的长度输出。
  • prec:这是一个数字值,制定了浮点数中小数点后面位数,或者文本字符串中显示的最大字符数。
  • -(减号):指明在向格式化空间中放入数据时采用左对齐而不是右对齐。

举一个之前的例子:

$ cat test4.txt
oldmanw
No.10 XiTuCheng Road
Beijing
(010)6228-1234

handsomeBoy
No.10 XiTuCheng Road
Beijing
12345678901
$ awk 'BEGIN {FS="\n"; RS=""} {print $1, $4}' test4.txt
oldmanw (010)6228-1234
handsomeBoy 12345678901

可以用printf命令来帮助格式化输出,使得输出信息看起来更加美观。首先将print命令替换成printf命令:

$ awk 'BEGIN {FS="\n"; RS=""} {printf "%s %s\n", $1, $4}' test4.txt
oldmanw (010)6228-1234
handsomeBoy 12345678901

需要注意的是,你需要在printf的末尾手动添加换行符\n来生成新行,如果没有添加,printf命令会在同一行打印后续输出:

$ awk 'BEGIN {FS="\n"; RS=""} {printf "%s %s", $1, $4}' test4.txt
oldmanw (010)6228-1234handsomeBoy 12345678901

如果需要用几个单独的printf命令在同一行上打印多个输出,可以这么写:

$ cat test2.txt
data11,data12,data13
data21,data22,data23
data31,data32,data33
$ awk -F, '{printf "%s ", $1} END {printf "\n"}' test2.txt
data11 data21 data31

每个printf的输出都会出现在同一行,为了终止该行,END部分打印了换行符。

下一步,用修饰符来格式化第一个字符串值:

$ awk 'BEGIN {FS=","; RS=""} {printf "%16s   %s\n", $1, $4}' test4.txt
         oldmanw   (010)6228-1234
     handsomeBoy   12345678901

通过一个值为16的修饰符,我们强制将第一个字符的输出宽度为16个字符。如果要显示的字符串长度超过限制的宽度,会将字符串的前n个字符占满限制宽度(n为设置的宽度限制),剩下的字符也会显示,并将剩余部分依次右移:

$ awk 'BEGIN {FS=","; RS=""} {printf "%8s   %s\n", $1, $4}' test4.txt
 oldmanw   (010)6228-1234
handsomeBoy   12345678901

在默认情况下,printf命令使用右对齐,如果要改成左对齐,只需要给修饰符加一个减号即可:

$ awk 'BEGIN {FS=","; RS=""} {printf "%-16s   %s\n", $1, $4}' test4.txt
oldmanw            (010)6228-1234
handsomeBoy        12345678901

printf在处理浮点值时也非常方便:

$ awk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
>     total += $i
> }
> avg = total / 3
> printf "Average: %5.1f\n", avg
> }' test6.txt
Average: 128.3
Average: 137.7
Average: 176.7

其中的5.1表示输出共占5列,其中有1位小数。关于printf格式控制符更详细的介绍可以参考:关于printf()函数和浮点数

6. 内建函数

awk提供了不少内置函数,可以进行一些常见的数字、字符串以及时间函数的运算。可以利用这些内建函数来减少脚本中的编码工作。

(1)数学函数

下表列出了awk中内建的数学函数:

函数 描述
atan2(x, y) x/y的反正切,以弧度为单位
cos(x) x的余弦,以弧度为单位
exp(x) x的指数函数(e^x)
int(x) x的整数部分,取靠近0的一侧(int(5.6)=5, int(-5.6)=-5)
log(x) x的自然对数
rand() 比0大比1小的随机浮点值
sin(x) x的正弦,以弧度为单位
sqrt(x) x的平方根
srand(x) 为随机数指定一个种子值

除了标准数学函数外,awk还支持一些按位操作数据的函数:

函数 描述
and(v1, v2) v1和v2按位与运算
coml(val) val的补运算
lshift(val, count) 将val左移count位
or(v1, v2) v1和v2按位或运算
rshift(val, count) 将val右移count位
xor(v1, v2) v1和v2按位异或运算

(2)字符串函数

在这里不再详细介绍,具体可以参考:

awk字符串函数总结

awk内置字符串函数详解

(3)时间函数

awk中包含一些函数来帮助处理时间值,如下所示:

函数 描述
mktime(datespec) 将一个按YYYY MM DD HH MM SS[DST]格式指定的日期转换成时间戳
strftime(format [, timestamp]) 格式化时间输出,将时间戳转为时间字符串,如果没有timestamp默认对当前时间进行转换。format的具体格式见下表
systime() 得到时间戳,返回从1970年1月1日开始到当前时间的整秒数

strftime的格式说明符:

格式 描述
%a 星期几的缩写(Sun)
%A 星期几的完整写法(Sunday)
%b 月名的缩写(Oct)
%B 月名的完整写法(October)
%c 本地日期和时间
%d 十进制日期
%D 日期 08/20/99
%e 日期,如果只有一位会补上一个空格
%H 用十进制表示24小时格式的小时
%I 用十进制表示12小时格式的小时
%j 从1月1日起一年中的第几天
%m 十进制表示的月份
%M 十进制表示的分钟
%p 12小时表示法(AM/PM)
%S 十进制表示的秒
%U 十进制表示的一年中的第几个星期(星期天作为一个星期的开始)
%w 十进制表示的星期几(星期天是0)
%W 十进制表示的一年中的第几个星期(星期一作为一个星期的开始)
%x 重新设置本地日期(08/20/99)
%X 重新设置本地时间(12:00:00)
%y 两位数字表示的年(99)
%Y 当前月份
%Z 时区(PDT)
%% 百分号(%)

参考:linux awk 内置函数实例

时间函数常用来处理日志文件,而日志文件则常含有需要进行比较的日期。通过将日期的文本表示形式转换成时间戳的形式,能够轻松的比较日期。下面是在awk程序中使用时间函数的例子:

$ awk 'BEGIN {date=systime(); print strftime("%Y, %B %d, %A", date)}'
2018, November 20, Tuesday

7. 自定义函数

(1)定义和使用自定义函数

要定义自己的函数,必须使用function关键字。

function name([variables]) {

    statements

}

函数名必须能够唯一标识函数,可以在调用的awk程序中传给这个函数一个或多个变量。

在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块)

$ cat test4.txt
oldmanw
No.10 XiTuCheng Road
Beijing
(010)6228-1234

handsomeBoy
No.10 XiTuCheng Road
Beijing
12345678901
$ awk '
> function myprint()
> {
>     printf "%-16s - %s\n", $1, $4
> }
> BEGIN {FS="\n"; RS=""}
> {
>     myprint()
> }' test4.txt
oldmanw          - (010)6228-1234
handsomeBoy      - 12345678901

一旦定义了函数,你就能在程序的代码中随意使用。在涉及很大的代码量时,这会省去许多工作。

(2)创建库函数

显然,每次使用函数的时候都要重写一遍并不美妙,不过awk提供了一种途径来将多个函数放到一个库文件中,这样就能在所有的awk程序中使用。

首先需要创建一个存储所有awk函数的文件:

$ cat funclib
function myprint()
{
    printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
    return int(limit * rand())
}
function printthird()
{
    print $3
}

funclib文件中含有三个函数的定义。需要使用-f命令参数来使用它们,但是不能将-f和库函数文件单独使用,还需要一个包含函数的程序文件:

$ cat awkScript4
BEGIN {FS="\n"; RS=""}
{
    myprint()
}

然后在命令行上同时指定库文件和程序文件就可以了:

$ awk -f funclib -f awkScript4 test4.txt
oldmanw          - (010)6228-1234
handsomeBoy      - 12345678901

 

参考文献:Linux命令行与shell脚本编程大全(第3版)

你可能感兴趣的:(awk命令详解(二))