十八、gawk进阶
gawk支持两种变量
- 内建变量
- 自定义变量
内建变量
用来存放处理数据文件中的数据字段。
如数据字段变量
第一个数据字段变量$1表示,第二个用$2依次类推。
字段分隔符
模式空格或制表符
使用-F
常见的内建变量
变量 | 描述 |
FIELDWIDTHS | 由空格分隔的一列数字,定义每个数据字段确定宽度 |
FS | 输入字段分隔符 |
RS | 输入记录分隔符 |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
ARGC | 当前命令行参数个数 |
ENVIRON | 当前shell环境变量及其值组成的关联数组 |
NF | 数据文件中字段总数 |
FNR | 当前数据文件中的数据行数 |
NR | 已处理的输入记录数 |
OFS默认为空格,跟FS功能一样,但是是用在print命令输出上。
样本文件data1.txt
[root@tzPC 22Unit]# cat data1 data11,data12,data13,data14,data15 data21,data22,data23,data24,data25 data31,data32,data33,data34,data35
FS定义以逗号分隔字符
[root@tzPC 22Unit]# gawk 'BEGIN{FS=","}{print $1,$2,$3}' data1 data11 data12 data13 data21 data22 data23 data31 data32 data33
OFS变量能将定义输出分隔字符
[root@tzPC 22Unit]# gawk 'BEGIN{FS=",";OFS="-"}{print $1,$2,$3}' data1 data11-data12-data13 data21-data22-data23 data31-data32-data33
FIELDWIDTHS变量可以不靠字段分隔符来读取记录,而使用字段宽度来计算字段。
一旦设定FIELDWIDTHS变量就不能改了,不适用变长的字段。
样本文件data1b
[root@tzPC 22Unit]# cat data1b 1005.3247596.37 115-2.349194.00 05810.1298100.1
可以看到FIELDWIDTHS定义了分别以3个字符,5个字符,2个字符,5个字段长度分隔
[root@tzPC 22Unit]# gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data1b 100 5.324 75 96.37 115 -2.34 91 94.00 058 10.12 98 100.1
RS跟ORS默认为换行符
样本文件data2
[root@tzPC 22Unit]# cat data2 Riley Mullen 123 Main Street Chicago, IL 60601 (312)555-1234 Frank Williams 456 Oak Street Indianapolis, IN 46201 (317)555-9876 Haley Snell 4321 Elm Street Detroit, MI 48201 (313)555-4938
FS变量设置为换行符,表示每行都是一个单独的字段
RS设置为空字符串,表示每个空白行当作一个记录分隔符
输出每条记录的第一个字段跟第四个字段,注意以空行分隔为一条记录,即1234行为一条记录,以\n为分隔字符,即1行为一个字段,输出14字段即$1跟$4
[root@tzPC 22Unit]# gawk 'BEGIN{FS="\n"; RS=""}{print $1,$4}' data2 Riley Mullen (312)555-1234 Frank Williams (317)555-9876 Haley Snell (313)555-4938
ARGC和ARGV变量
这两个变量可以获取命令行参数的总数以及他们的值。
[root@tzPC 22Unit]# gawk 'BEGIN{print ARGC,ARGV[1]}' data1 2 data1
ARGC表明命令行上有2个参数,分别是gawk跟data1这两个
ARGV数组是从索引0开始,0代表命令gawk,1代表参数data1,中间的脚本不算参数
[root@tzPC 22Unit]# gawk 'BEGIN{print ARGC,ARGV[0]}' data1 2 gawk [root@tzPC 22Unit]# gawk 'BEGIN{print ARGC,ARGV[1]}' data1 2 data1 [root@tzPC 22Unit]# gawk 'BEGIN{print ARGC,ARGV[2]}' data1 2 [root@tzPC 22Unit]#
ENVIRON变量
使用关联数组来提取shell环境变量
关联数组以文本如环境变量名HOME作为索引值
[root@tzPC 22Unit]# gawk ' > BEGIN{ > print ENVIRON["HOME"] > print ENVIRON["PATH"] > }' /root /mnt/mysql/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin [root@tzPC 22Unit]#
NF变量
样本文件/etc/passwd
[root@tzPC 22Unit]# cat /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
FS变量设置以:分隔,OFS变量设置以:输出,NF变量为字段总数,$NF变量为最后一个数据字段的值
[root@tzPC 22Unit]# gawk 'BEGIN{FS=":"; OFS=":"}{print $1,NF}' /etc/passwd root:7 bin:7 daemon:7 adm:7 lp:7 [root@tzPC 22Unit]# gawk 'BEGIN{FS=":"; OFS=":"}{print $1,$NF}' /etc/passwd root:/bin/bash bin:/sbin/nologin daemon:/sbin/nologin adm:/sbin/nologin lp:/sbin/nologin
FNR变量
样本文件data1
[root@tzPC 22Unit]# cat data1 data11,data12,data13,data14,data15 data21,data22,data23,data24,data25 data31,data32,data33,data34,data35 [root@tzPC 22Unit]#
gawk命令行定义了两个输入文件data1,脚本会打印第一个数据字段的值和FNR变量的当前值,gawk程序处理第二个数据文件时,FNR值归1
[root@tzPC 22Unit]# gawk 'BEGIN{FS=","}{print $1,"FNR="FNR}' data1 data1 data11 FNR=1 data21 FNR=2 data31 FNR=3 data11 FNR=1 data21 FNR=2 data31 FNR=3
NR变量
还是data1样本文件
gawk程序在处理第二个数据文件时,NR值继续没有归1而是继续计数
[root@tzPC 22Unit]# gawk 'BEGIN{FS=","}{print $1,"FNR="FNR,"NR="NR}' data1 data1 data11 FNR=1 NR=1 data21 FNR=2 NR=2 data31 FNR=3 NR=3 data11 FNR=1 NR=4 data21 FNR=2 NR=5 data31 FNR=3 NR=6
自定义变量
变量命名规则
任意长度的字母、数字、下划线
不能以数字开头
区分大小写
在脚本中给变量赋值
[root@tzPC 22Unit]# gawk ' > BEGIN{ > testing="This is a test" > print testing > testing=45 > print testing > }' This is a test 45
使用算数运算
[root@tzPC 22Unit]# awk 'BEGIN{x=4; x=x*2+3; print x}' 11
在命令行上给变量赋值
输出data1每行第三个字段
[root@tzPC 22Unit]# cat script1 BEGIN{FS=","} {print $n} [root@tzPC 22Unit]# gawk -f script1 n=3 data1 data13 data23 data33
要让变量在BEGIN中使用需要在gawk后接-v选项,注意-v必须放在脚本代码前
[root@tzPC 22Unit]# cat script2 BEGIN{print "The starting value is " n; FS=","} {print $n} [root@tzPC 22Unit]# gawk -v n=3 -f script2 data1 The starting value is 3 data13 data23 data33
定义数组
gawk编程语言使用关联数组,即使用字符串作为数组的索引值。
语法
var[index] = element
var是变量名,index是索引值,element数组元素值
[root@tzPC 22Unit]# gawk 'BEGIN{ > capital["Illinois"] = "Springfield" > print capital["Illinois"] > }' Springfield
算数运算
[root@tzPC 22Unit]# gawk 'BEGIN{ > var[1] = 34 > var[2] = 3 > total = var[1] + var[2] > print total > }' 37
遍历数组
使用for循环
这里需要注意的是,test存储的是索引值,var是变量名
索引值输出是无序的,以agmn定义,并不会以agmn顺序输出
[root@tzPC 22Unit]# gawk 'BEGIN{ > var["a"] = 1 > var["g"] = 2 > var["m"] = 3 > var["n"] = 4 > for (test in var) > { > print "Index:",test," Value:",var[test] > } > }' Index: m Value: 3 Index: n Value: 4 Index: a Value: 1 Index: g Value: 2
删除数组变量
这里有个有趣的地方,这里for 可以不写do-done跟{},写在一行跟换行写都能执行成功
[root@tzPC 22Unit]# gawk 'BEGIN{ var["a"] = 1 var["g"] = 2 for (test in var) { print "Index:",test," Value:",var[test] } delete var["g"] print "数组键值g已删除" for (test in var) print "Index:",test," Value:",var[test] }'
Index: a Value: 1 Index: g Value: 2 数组键值g已删除 Index: a Value: 1
正则表达式
正则表达式必须出现在它要控制的程序脚本前
样本文件
[root@tzPC 22Unit]# cat data1 data11,data12,data13,data14,data15 data21,data22,data23,data24,data25 data31,data32,data33,data34,data35 [root@tzPC 22Unit]#
如/11/正则表达式写在脚本前
[root@tzPC 22Unit]# gawk 'BEGIN{FS=","} /11/{print $1}' data1 data11
匹配操作符~
匹配操作符可以将正则表达式使用在特定的数据字段上。
$1表示第一个数据字段,此表达式会匹配第一个字段是以data开头的所有记录
$1 ~ /^data/
该脚本会匹配第二个字段是以data2开头的并打印出此行
[root@tzPC 22Unit]# gawk 'BEGIN{FS=","} $2 ~ /^data2/{print $0}' data1 data21,data22,data23,data24,data25
以:作为分隔符,第一个字段是以root开头的行,输出第一个字段跟最后一个字段
[root@tzPC 22Unit]# gawk -F: '$1 ~ /root/{print $1,$NF}' /etc/passwd root /bin/bash
可以使用!排除正则表达式的匹配
[root@tzPC 22Unit]# gawk -F: '$1 !~ /root/{print $1,$NF}' /etc/passwd bin /sbin/nologin daemon /sbin/nologin adm /sbin/nologin lp /sbin/nologin ...
使用数学表达式
显示第四个字段即组ID为0的用户
[root@tzPC 22Unit]# gawk -F: '$4 == 0{print $1}' /etc/passwd root sync shutdown halt operator
还能使用比较表达式
==、<=、<、>=、>等
[root@tzPC 22Unit]# gawk -F, '$1 == "data11" {print $1}' data1 data11
使用结构化命令if
语法格式
if (condition) statement1 #或者 if (condition) statement1
样本文件data4
[root@tzPC 22Unit]# cat data4 23 123 4 45 56 7 23
找出大于20的第一个字段
[root@tzPC 22Unit]# gawk '{if ($1 > 20) print $1}' data4 23 123 45 56 23
如果要在if语句中使用多条命令需要使用{}
[root@tzPC 22Unit]# gawk '{ > if ($1 >20) > { > x= $1 * 2 > print x > } > }' data4 46 246 90 112 46
if-else语句
[root@tzPC 22Unit]# gawk '{ > if ($1 > 20) > { > x= $1 * 2 > print x > } else > { > x= $1 /2 > print x > }}' data4 46 246 2 90 112 3.5 46
如果单行写,if语句部分需要加分号
[root@tzPC 22Unit]# gawk '{if ($1 > 20) print $1 * 2; else print $1 / 2 }' data4 46 246 2 90 112 3.5 46
while语句
样本文件data5
[root@tzPC 22Unit]# cat data5 155 158 121 121 154 165 188 164 125
计算每行平均值
[root@tzPC 22Unit]# gawk '{ > total = 0 > i = 1 > while (i < 4) > { > total += $i > i++ > } > avg = total /3 > print "Average:",avg > }' data5 Average: 144.667 Average: 146.667 Average: 159
可以使用break跟continue语句
计算每行前两个数的平均值
[root@tzPC 22Unit]# gawk '{ > 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 > }' data5 The average of the first two data elements is: 156.5 The average of the first two data elements is: 137.5 The average of the first two data elements is: 176
average平均,elements元素
do-while语句
类似while语句,但会在检查条件之前执行命令。
该语句保证在检查条件之前至少先执行一次命令。
读取样本文件data5中每行的每个字段并将它累加到一起,直到>=150后输出
[root@tzPC 22Unit]# gawk '{ > total = 0 > i = 1 > do > { > total += $i > i++ > } while (total <150) > print total }' data5 155 275 188
for语句
gawk编程语言支持C风格的for循环
同样计算data5每行平均值
[root@tzPC 22Unit]# gawk '{ > total = 0 > for (i = 1; i < 4; i++) > { > total += $i > } > avg = total /3 > print "Average:",avg > }' data5 Average: 144.667 Average: 146.667 Average: 159
格式化打印printf命令
语法格式
print "format string", var1, var2 ...
format string格式化指定符,指定变量如何显示。
第一个format string对应第一个变量,依次类推
格式化指定符控制字母
控制字母 | 描述 | 控制字母 | 描述 |
c | 将一个数作为ASCII字符显示 | g | 用科学计数法或浮点数显示 |
d | 显示一个整数 | o | 显示一个八进制数 |
i | 显示一个整数 | s | 显示一个文本字符串 |
e | 用科学计数法显示一个数 | x | 显示一个十六进制数 |
f | 显示一个浮点数 | X | 显示一个十六进制,但用大写字母A~F |
注意使用控制字母前需要加上%
用科学计数法显示一个很大的数值
[root@tzPC 22Unit]# gawk 'BEGIN{ > x = 10 * 100 > printf "The answer is: %e\n",x > }' The answer is: 1.000000e+03
出了控制字母,还有3种修饰符
- width:只当输出字段长度,如果小于这个值,printf会将文本右对齐并以空格填充,如果大于这个值,则按实际长度输出。
- prec:指定小数点后几位
- -:采用左对齐
样本文件data2
[root@tzPC 22Unit]# cat data2 Riley Mullen 123 Main Street Chicago, IL 60601 (312)555-1234 Frank Williams 456 Oak Street Indianapolis, IN 46201 (317)555-9876 Haley Snell 4321 Elm Street Detroit, MI 48201 (313)555-4938
使用print输出
[root@tzPC 22Unit]# gawk 'BEGIN{FS="\n"; RS=""}{print $1,$4}' data2 Riley Mullen (312)555-1234 Frank Williams (317)555-9876 Haley Snell (313)555-4938
使用printf输出
[root@tzPC 22Unit]# gawk 'BEGIN{FS="\n"; RS=""}{printf "%s %s\n", $1, $4}' data2 Riley Mullen (312)555-1234 Frank Williams (317)555-9876 Haley Snell (313)555-4938
你会发现没有差别,是不是很惊喜!哈哈!
指定每行第一个字段输出宽度为16个字符,默认是右对齐
[root@tzPC 22Unit]# gawk 'BEGIN{FS="\n"; RS=""}{printf "%16s %s\n", $1, $4}' data2 Riley Mullen (312)555-1234 Frank Williams (317)555-9876 Haley Snell (313)555-4938
加-变成左对齐
[root@tzPC 22Unit]# gawk 'BEGIN{FS="\n"; RS=""}{printf "%-16s %s\n", $1, $4}' data2 Riley Mullen (312)555-1234 Frank Williams (317)555-9876 Haley Snell (313)555-4938
使用%15.2f格式指定printf输出将浮点值近似到小数点后两位,15是宽度值
[root@tzPC 22Unit]# gawk '{ total = 0 for (i = 1; i < 4; i++) { total += $i } avg = total /3 printf "Average: %15.2f\n",avg }' data5 Average: 144.67 Average: 146.67 Average: 159.00
内建函数
gawk编程语言提供了不少内置函数,加快编码效率。
数学函数
gawk数学函数
函数 | 描述 | 函数 | 描述 |
atan2(x,y) | x/y的反正切 | rand() | 取大于0小于1的随机浮点值 |
cos(x) | x的余弦 | sin(x) | x的正弦 |
int(x) | x的整数部分 | sqrt(x) | x的平方根 |
log(x) | x的自然对数 | srand(x) | 为计算随机数指定一个初始值 |
看到这几个算数符有没有回到初中的感觉哈哈。
int()相当于转整型,值为5.6时取5,不会四舍五入
rand()比较常用,取随机数。
返回0~9的随机数
x = int(10 * rand())
gawk语言对处理的数值有确定范围,如果数值过大会报错
[root@tzPC 22Unit]# gawk 'BEGIN{x=exp(1000);print x}' gawk: cmd. line:1: warning: exp: argument 1000 is out of range inf [root@tzPC 22Unit]#
gawk还支持按位操作数据的函数,对处理二进制值很有用,我目前暂时不需要,在书P488
字符串函数
书P488有个表,太多了,写几个常用的就行了
小写转大写
toupper(s)
[root@tzPC 22Unit]# gawk 'BEGIN{x = "testing"; print toupper(x);print length(x)}' TESTING 7
asort(s,d)
将数组s按数据元素值排序,排序后的数组会储存在数组d中
[root@tzPC 22Unit]# gawk 'BEGIN{ > var["a"] = 1 > var["b"] = 2 > var["c"] = 3 > var["d"] = 4 > asort(var, test) > for (i in test) > print "Index:",i," value:",test[i] > }' Index: 4 value: 4 Index: 1 value: 1 Index: 2 value: 2 Index: 3 value: 3
可以看到,test的索引值跟数据元素都已经变成数组var排序后的数据元素值了,且asort是按照ascii码排序的。
asorti(s,d)
将数组s按索引排序,排序后的数组会储存在数组d中
split(s, a [r])
将s使用FS字符或正则表达式r(可选)处理后分开放到数组a中
将data1中的每行使用逗号分割,放入数组var中,打印索引1跟5中的元素
[root@tzPC 22Unit]# gawk 'BEGIN{ FS=","}{ > split($0,var) > print var[1], var[5] > }' data1 data11 data15 data21 data25 data31 data35
时间函数
gawk的时间函数
函数 | 描述 |
mktime(datespec) | 将YYYY MM DD HH MM SS格式指定的日期转换成时间戳 |
strftime(format timestamp) | 将当前时间戳或timestamp(可选)转换成格式化日期 |
systime() | 返回当前时间戳 |
[root@tzPC 22Unit]# gawk 'BEGIN{ > date = systime() > day = strftime("%A, %B %D, %Y", date) > print day > }' Saturday, September 09/19/20, 2020 [root@tzPC 22Unit]# gawk 'BEGIN{ date = systime() day = strftime("%A, %B %d, %Y", date) print day }' Saturday, September 19, 2020
自定义函数
语法格式
function name(variables) { statements
return value #返回值 }
将返回值赋值给变量
x = myrand(100)
函数必须定义在所有代码块之前(包括BEGIN代码块)
[root@tzPC 22Unit]# gawk ' function myprint() { printf "%-16s %s\n", $1, $4 } BEGIN{FS="\n"; RS=""} { myprint() }' data2 Riley Mullen (312)555-1234 Frank Williams (317)555-9876 Haley Snell (313)555-4938
使用函数库
总所周知,函数库相当于类。
先创建gawk函数文件,也就是相当于java类
[root@tzPC 22Unit]# cat funclib function myprint() { printf "%-16s %s\n", $1, $4 } function printthird() { print $3 } function myrand(limit) { return int(limit * rand()) }
创建脚本文件
[root@tzPC 22Unit]# cat script4 BEGIN{ FS="\n"; RS=""} { myprint() }
在命令行上使用-f指定函数库文件以及脚本文件
[root@tzPC 22Unit]# gawk -f funclib -f script4 data2 Riley Mullen (312)555-1234 Frank Williams (317)555-9876 Haley Snell (313)555-4938
学习来自:《Linux命令行与Shell脚本大全 第3版》第22章