Linux之(14)gawk进阶

Linux之shell(6)gawk进阶

Author:onceday Date:2022年11月1日

全系列文章请查看专栏: Linux Shell基础_Once_day的博客-CSDN博客

漫漫长路,有人对你微笑过嘛…

本文主要收集整理于以下文档:

  • 《Linux命令行与shell脚本编程大全》
  • 《鸟哥的Linux私房菜》

1.使用变量

gawk编程语言支持两种不同类型的变量:

  • 内建变量
  • 自定义变量

gawk有一些内建变量,这些变量存放用来处理数据文件中的数据字段和记录的信息。也可以在gawk程序中创建自己的变量。

1.1 内建变量

数据字段变量是gawk中的一种内建变量类型,即使用$n来引用第n个位置的字段。如下:

  • $1引用记录中的第一个数据字段。
  • $2引用第二个字段。

数据字段是有字符分割符来划定的,默认情况下,字段分割符是一个空白字符,也就是空格符或者制表符

下面是常见的数据字段和记录变量:

变量描述
FIELDWIDTHS由空格分割的一列数字,定义了每个数据字段确切宽度
FS输入字段分隔符
RS输入记录分隔符
OFS输出字段分隔符
ORS输出记录分隔符

下面是使用的例子:

onceday@ubuntu:shell$ cat data1.txt
data11,data12,data13,data14
data21,data22,data23,data24
data31,data32,data33,data34
onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} {print $1,$2,$3}' data1.txt
data11 data12 data13
data21 data22 data23
data31 data32 data33

可以看到,输入的字段分隔符由FS指定,而RS默认为\n,因此每一行为一个记录,每个记录按照,来区分不同的字段,前三个字段即上述对应的文本。输出OFSORS都是默认字符,因此为[space]\n

onceday@ubuntu:shell$ gawk 'BEGIN{FS=",";OFS="-"} {print $1,$2,$3}' data1.txt
data11-data12-data13
data21-data22-data23
data31-data32-data33
onceday@ubuntu:shell$ gawk 'BEGIN{FS=",";OFS="-";ORS=";;;"} {print $1,$2,$3}' data1.txt
data11-data12-data13;;;data21-data22-data23;;;data31-data32-data33;;;onceday@ubuntu:shell$ 

FIELDWIDTHS变量允许直接按照字段的宽度来读取指定文本,这是专门用于应对没有字段分割符的数据

onceday@ubuntu:shell$ gawk 'BEGIN{FIELDWIDTHS="4 3 4 3 4 3 6"} {print $1,$2,$3,$4,$7}' data1.txt 
data 11, data 12, data14
data 21, data 22, data24
data 31, data 32, data34

下面是一个复杂的具体情况:

Riley Mullen
123 Main Street
Chicago, IL 60601
(312)55-1234

对于这种情况,需要把FS变量设为换行符,表明数据流中的每行都是一个单独的字段,每行上的所有数据都属于同一个字段。为了判断一个记录从何开始,可以把记录分割符RS设置为“”,即空字符串,因为gawk会把空白行当成空字符串,即记录分割符。

1.2 数据变量

gawk提供了很多变量来辅助工作,比如提取shell环境的信息。

变量描述
ARGC当前命令行参数个数
ARGIND当前文件在ARGV中的位置
ARGV包含命令行参数的数组
CONVFMT数字的转换格式(printf语句),默认值为%.6 g
ENVIRON当前shell环境变量及其值组成的关联数组
ERRNO当读取或关闭输出文件发生错误时的系统错误号
FILENAME用作gawk输入数据的数据文件的文件名
FNR当前数据文件中的数据行数
IGNORECASE设成非零值时,忽略gawk命令中出现的字符串的字符大小写
NF数据文件中的字段总数
NR已处理的输入记录数
OFMT数字的输出格式,默认值为%.6g
RLENGTH由match函数所匹配的子字符串的长度
RSTART有match函数所匹配的子字符串的起始位置

其中ARGCARGV是从shell中获取的命令行参数,如下:

onceday@ubuntu:shell$ gawk 'BEGIN{print ARGC,ARGV[0],ARGV[1]}' data1.txt
2 gawk data1.txt

可以发现,在脚本中引用gawk变量,变量名前面不加美元符

ENVIRON变量使用关联数组来提取shell环境变量,关联数组用作数组的索引值,而不是竖直。

onceday@ubuntu:shell$ gawk 'BEGIN{print ENVIRON["HOME"]}' data1.txt
/home/onceday

使用方法类似于字典,可以将shell的环境变量提取出来。

NF变量可以在不知道具体位置的情况下,指定记录中的最后一个数据字段。

需要注意的是,NF是含有最后一个行数的值,因此需要结合字段变量来使用

onceday@ubuntu:shell$ cat data1.txt 
data11,data12,data13,data14
data21,data22,data23,data24
data31,data32,data33,data34
onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} {print $1,$NF}' data1.txt
data11 data14
data21 data24
data31 data34

FNR是当前数据文件中的已处理过的记录数,NR是已处理过的记录总数,可能包含多个文件

onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} {print $1,"FNR="FNR,"NR="NR}' data1.txt data1.txt 
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

可以清楚看到,FNR的值在处理第二个数据文件时被重置了,而NR变量则在处理第二个数据文件时,继续计数

1.3 自定义变量

gawk允许你定义自己的变脸并在程序代码中使用,gawk自定义变量名可以是任意数目的字母、数字和下划线,但是不能以数字开头。并且,要记住gawk变量名区分大小写

可以直接在脚本中给变量赋值:

onceday@ubuntu:shell$ gawk 'BEGIN{testing="this is a test";print testing}'
this is a test

也可以使用数学算式来处理数字值。

onceday@ubuntu:shell$ gawk 'BEGIN{x=4;x=x * 2 + 3;print x}'
11

gawk命令行上也可以直接给变量赋值

onceday@ubuntu:shell$ cat script1 
BEGIN{FS=","}
{print $n}
onceday@ubuntu:shell$ gawk -f script1 n=2 data1.txt
data12
data22
data32

但是有一个问题,这个变量值在BEGIN部分不可用,因此需要添加额外的命令参数-v

onceday@ubuntu:shell$ gawk 'BEGIN{x=n; x=x * 2 + 3;print x}' n=5
3
onceday@ubuntu:shell$ gawk -v n=5 'BEGIN{x=n; x=x * 2 + 3;print x}'
13
2.处理数组

gawk提供关联数组用于数组功能,它的索引值可以各种字符串,且每个索引字符串都需要能够唯一标识出赋给它的数据元素。

下面可以定义一个数组元素:

var[index] = element

var是变量名,index是关联数组的索引值,element是数据元素值。

name["student1"] = "Mike"
name["student2"] = "Job"
name["student3"] = "Bob"
onceday@ubuntu:shell$ gawk 'BEGIN{name["st1"] = "bob";print name["st1"]}' data1.txt 
bob

使用非常简单,使用存储时的索引值去取值即可,也可以当成数组来使用:

onceday@ubuntu:shell$ gawk 'BEGIN{
> var[1]=21
> var[2]=41
> var[3]=54
> var[4]=64
> print var[1]+var[2]
> }'
62

遍历数组变量需要使用for语法,因为关联数组无法确定索引值是什么。

for (var in array)
{
	statements
}

该语句每次循环时将关联数组array的下一个索引值赋给变量var,然后执行一遍statements,重要的是记住这个变量中存储的索引值而不是数组元素值。

onceday@ubuntu:shell$ gawk 'BEGIN{
>var[1]=21
>var[2]=41
>var[3]=54
>var[4]=64
>for (key in var) {
>print "Index:",key," - value:",var[key]
>}
>}'
Index: 1  - value: 21
Index: 2  - value: 41
Index: 3  - value: 54
Index: 4  - value: 64

需要注意,并不能假设索引值key能按固定的顺序来返回,但是key总会遍历完成

删除数组变量需要使用以下语法:

delete array[index]

一旦从关联数组中删除了索引值,就没有办法再用它来提取元素值

3.使用模式匹配

gawk支持正则表达式,包括BRE基础模式和ERE扩展模式。

onceday@ubuntu:shell$ cat data1.txt 
data11,data12,data13,data14
data21,data22,data23,data24
data31,data32,data33,data34
onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} /22/{print $1}' data1.txt
data21

gawk程序会用正则表达式对记录中所有的数据字段进行匹配,包括字段分割符。

匹配操作符(matching operator)允许将正则表达式限定在记录中的特定数据字段,匹配操作符是波浪线~

$1 ~ /^data/

$1变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本data开头的所有记录。

onceday@ubuntu:shell$ cat data1.txt 
data11,data12,data13,data14
data21,data22,data23,data24
data31,data32,data33,data34
onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} $1 ~ /data2/{print $0}' data1.txt
data21,data22,data23,data24

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

$1 !~ /expression/

也可以在匹配模式中使用数学表达式,这个功能在匹配数据字段中的数字值时非常方便

onceday@ubuntu:shell$ cat num.txt 
12,47,875,58,658
542,587,589,5632,45
78,54,69,74,653
onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} $2 > 100{print $0}' num.txt
542,587,589,5632,45

在数学表达式中可以使用常见的数学比较表达式:

x == y: 值x等于y
x <= y: 值x小于等于y
x < y : 值x小于y
x >= y: 值x大于等于y
x > y : 值x大于y

对于文本数据也可以使用匹配,但表达式必须完全匹配,数据和模式严格匹配:

onceday@ubuntu:shell$ cat num.txt
12,47,875,58,658
542,587,589,5632,45
78,54,69,74,653
onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} $2 == "47"{print $0}' num.txt
12,47,875,58,658
4.结构化命令
4.1 if语句
if (condition)
	statement1

需要注意,if语句的花括号不能和其他地方的花括号弄混,必须独立出来:

onceday@ubuntu:shell$ cat num.txt
12,47,875,58,658
542,587,589,5632,45
78,54,69,74,653
onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} {if ($2 > 50) { print $0}}' num.txt
542,587,589,5632,45
78,54,69,74,653

if是支持else子语句的,单行上需要添加分号:

if (condition) {statement1}; else {statement2};
4.2 while语句

while语句为gawk程序提供了一个基本的循环功能,下面是while语句的格式。

while (condition)
{
	statements
}

while循环允许遍历一组数据,并检查迭代的结束条件。

onceday@ubuntu:shell$ cat num.txt 
12,47,875,58,658
542,587,589,5632,45
78,54,69,74,653
onceday@ubuntu:shell$ gawk 'BEGIN{FS="," };{
total = 0
i = 1
while (i < 4)
{
total += $i
i++
}
avg = total / 3
print "Average:",avg
}' num.txt
Average: 311.333
Average: 572.667
Average: 67

在while语句中也可以使用breakcontinue语句,允许从循环中跳出

4.3 do-while语句

类似于while语句,但会在检查语句条件之前执行命令。

do {
	statements
} while (condition)

该语句会在求值之前先执行一次,其他和while没有区别

4.4 for 语句

风格和C语言是一致的。

for (variable assignment; condition; iteration process)
onceday@ubuntu:shell$ gawk '
> BEGIN {FS=","}
> { total = 0
> for (i = 1; i < 4; i++)
> {
>    total += $i
> }
> avg = total / 3
> print "Average:",avg
> }' num.txt
Average: 311.333
Average: 572.667
Average: 67
5.格式化打印

下面是printf命令格式:

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

该命令的使用方法和C语言类似。如下形式:

%[modifier]control-letter

下面是可选的格式化指定符中的控制字母:

控制字母描述
c将一个数作为ASCII字符显示
d显示一个整数值
i显示一个整数值(跟d一样)
e用科学计数法显示一个数
f显示一个浮点值
g用科学计数法或浮点数显示(选择较短的形式)
o显示一个八进制
s显示一个文本字符串
x显示一个十六进制值
X显示一个十六进制值,但用大写字母A-F

下面是实例:

onceday@ubuntu:shell$ gawk 'BEGIN{
> x = 10 * 100
> printf "The answer is: %e\n",x
> }'
The answer is: 1.000000e+03

除了上面所示的几种字符,还有3种修饰符可以进一部控制输出。

  • width,指定了输出字段最小宽度的数字值。如果输出短于这个值,printf会将文本右对齐,并用空格进行填充。如果输出比指定的宽度还要长,则按照实际的长度输出。
  • prec,这是一个数字值,指定了浮点数中小数点后面位数,或者文本字符串中显示的最大字符数。
  • -,指明在向格式化空间中放入数据时采用左对齐而不是右对齐。
6.内建函数
6.1数学函数
函数描述
atan2(x, y)x/y的反正切,x和y以弧度为单位
cos(x)x的余弦,x以弧度为单位
exp(x)x的指数部分
int(x)x的整数部分,去靠近零一侧的值
log(x)x的自然对数
rand()比0大比1小的随机浮点值
sin(x)x的正弦,x以弧度为单位
sqrt(x)x的平方根
srand(x)为计算随机数指定一个种子值
6.2 位操作函数
函数描述
and(v1, v2)执行值v1和v2的按位与运算
compl(val)执行val的补运算
lshift(val,count)将值val左移count位
or(v1,v2)执行值v1和v2的按位或运算
rshift(val,count)将值val右移count位
xor(v1, v2)执行值v1和v2的按位异或运算
6.3 字符串函数
函数描述
asort(s [,d])将数组按照数据元素值排序,可指定存在d中。索引值被替换成表示新的排序顺序的连续数字
asorti(s [,d])按照数据元素值排序,可指定存在d中.索引值作为数据元素值,连续数字索引表示排序顺序
gensub(r, s, h [,t])查找变量$0或者目标t,来匹配正则表达式r,如果h是一个以g或G开头的字符串,就用s替换掉匹配的文本。如果h是一个数字,它表示要替换掉第n处r匹配的地方。
gsub(r, s [,t])查找变量$0或者目标字符串t,来匹配正则表达式r,如果找到了,就全部替换为字符串s。
index(s, t)返回字符串t在字符串s中的索引值,如果没有找到的话返回0
length([s])返回字符串s的长度,如果没有指定的话,返回$0的长度
match(s,r [,a])返回字符串s中正则表达式r出现位置的索引,如果指定了数组a,它会存储s中匹配正则表达式的那部分。
split(s,a,[,r])将s用FS字符或正则表达式r分开放到数组a中,返回字段的总数
sprintf(format,variables)用提供的format和variable返回一个类似于print输出的字符串
sub(r,s [,t])在变量$0或目标字符串t中查找正则表达式r的匹配,如果找到了,就用字符串s替换掉第一处匹配。
substr(s, i [,n])返回s中从索引值i开始的n个字符组成的子字符串。如果未提供n,则返回s剩下的部分。
tolower(s)将s中的所有字符转换为小写
toupper(s)将s中的所有字符转换为大写
6.4 时间函数
函数描述
mktime(datespec)将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值
strftime(format [, timestamp])将当前时间的时间戳货timestamp转化格式化日期(采用shell函数date()的格式)
systime()返回当前时间的时间戳
6.5 自定义函数

定义函数:

function name ([variables])
{
	statements
}

可以将自己写的gawk函数保存在一个函数库中,然后如下方式使用:

gawk -f funclib -f script4 data1

下面是一个实例:

onceday@ubuntu:shell$ cat num.txt 
12,47,875,58,658
542,587,589,5632,45
78,54,69,74,653
onceday@ubuntu:shell$ gawk '
function myprint()
{
    printf "%-16s - %s\n", $1, $2
}
BEGIN{FS=","}
{
    myprint()
}' num.txt
12               - 47
542              - 587
78               - 54
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值