Linux Shell 文本处理进阶之gawk

01. 使用变量

01. 内建变量

gawk程序使用内建变量来引用程序数据里的一些特殊功能。

字段和记录分隔符变量

数据字段变量允许你使用美元符号($)和字段在该记录中的位置值来引用记录对应的字段。因此,要引用记录中的第一个数据字段,就用变量$1;要引用第二个字段,就用$2,依次类推。

数据字段是由字段分隔符来划定的。默认情况下,字段分隔符是一个空白字符,也就是空格符或者制表符。使用命令行参数-F或者在gawk程序中使用特殊的内建变量FS来更改字段分隔符。

变 量 			描 述
FIELDWIDTHS 	由空格分隔的一列数字,定义了每个数据字段确切宽度
FS 				输入字段分隔符
RS 				输入记录分隔符
OFS 			输出字段分隔符
ORS 			输出记录分隔符
cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35

gawk 'BEGIN{FS=","} {print $1,$2,$3}' data1
data11 data12 data13
data21 data22 data23
data31 data32 data33

gawk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' data1
data11-data12-data13
data21-data22-data23
data31-data32-data33

一旦设置了FIELDWIDTH变量, gawk就会忽略FS变量,并根据提供的字段宽度来计算字段。

cat data1b
1005.3247596.37
115-2.349194.00
05810.1298100.1

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

默认情况下, gawk将RS和ORS设为换行符。默认的RS值表明,输入数据流中的每行新文本就是一条新纪录。

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
4231 Elm Street
Detroit, MI 48201
(313)555-4938

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

ARGC变量表明命令行上有两个参数。这包括gawk命令和data1参数(记住,程序脚本并不算参数)。 ARGV数组从索引0开始,代表的是命令。(跟shell变量不同,在脚本中引用gawk变量时,变量名前不加美元符。)

ENVIRON变量看起来可能有点陌生。它使用关联数组来提取shell环境变量。关联数组用文本作为数组的索引值,而不是数值。

gawk '
> BEGIN{
> print ENVIRON["HOME"]
> print ENVIRON["PATH"]
> }'
/home/rich
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin

当要在gawk程序中跟踪数据字段和记录时,变量FNR、 NF和NR用起来就非常方便。NF变量含有数据文件中最后一个数据字段的数字值。可以在它前面加个美元符将其用作字段变量。

gawk 'BEGIN{FS=":"; OFS=":"} {print $1,$NF}' /etc/passwd
rich:/bin/bash
testy:/bin/csh
mark:/bin/bash
dan:/bin/bash
mike:/bin/bash
test:/bin/bash

FNR变量含有当前数据文件中已处理过的记录数,NR变量则含有已处理过的记录总数。

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

FNR变量的值在gawk处理第二个数据文件时被重置了,而NR变量则在处理第二个数据文件时继续计数。结果就是:如果只使用一个数据文件作为输入, FNR和NR的值是相同的;如果使用多个数据文件作为输入, FNR的值会在处理每个数据文件时被重置,而NR的值则会继续计数直到处理完所有的数据文件。

gawk '
> BEGIN {FS=","}
> {print $1,"FNR="FNR,"NR="NR}
> END{print "There were",NR,"records processed"}' 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
There were 6 records processed
02. 自定义变量

gawk自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。重要的是,要记住gawk变量名区分大小写。

在脚本中给变量赋值

gawk '
> BEGIN{
> testing="This is a test"
> print testing
> testing=45
> print testing
> }'
This is a test

赋值语句还可以包含数学算式来处理数字值。

gawk 'BEGIN{x=4; x= x * 2 + 3; print x}'
11

在命令行上给变量赋值

cat script1
BEGIN{FS=","}
{print $n}

gawk -f script1 n=2 data1
data12
data22
data32

gawk -f script1 n=3 data1
data13
data23
data33

使用命令行参数来定义变量值会有一个问题。在你设置了变量后,这个值在代码的BEGIN部分不可用。

cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}

gawk -f script2 n=3 data1
The starting value is
data13
data23
data33

可以用-v命令行参数来解决这个问题。它允许你在BEGIN代码之前设定变量。

# 可以将shell变量传递给gawk程序
gawk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33

02. 处理数组

gawk编程语言使用关联数组提供数组功能。关联数组跟数字数组不同之处在于它的索引值可以是任意文本字符串。你不需要用连续的数字来标识数组中的数据元素。相反,关联数组用各种字符串来引用值。每个索引字符串都必须能够唯一地标识出赋给它的数据元素。

01. 定义数组变量

数组变量赋值的格式如下:

var[index] = element

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

capital["Illinois"] = "Springfield"
capital["Indiana"] = "Indianapolis"
capital["Ohio"] = "Columbus"

在引用数组变量时,必须包含索引值来提取相应的数据元素值。

gawk 'BEGIN{
> capital["Illinois"] = "Springfield"
> print capital["Illinois"]
> }'
Springfield
gawk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37
02. 遍历数组变量
for (var in array)
{
statements
}

这个for语句会在每次循环时将关联数组array的下一个索引值赋给变量var,然后执行一遍statements。重要的是记住这个变量中存储的是索引值而不是数组元素值。可以将这个变量用作数组的索引,轻松地取出数据元素值。

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

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

03. 删除数组变量

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

delete array[index]

删除命令会从数组中删除关联索引值和相关的数据元素值。

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

03. 使用模式

BEGIN和END关键字是用来在读取数据流之前或之后执行命令的特殊模式。

01. 正则表达式

可以用基础正则表达式(BRE)或扩展正则表达式(ERE)来选择程序脚本作用在数据流中的哪些行上。在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号前。

gawk 'BEGIN{FS=","} /11/{print $1}' data1
data11
02. 匹配操作符

匹配操作符是波浪线(~)。可以指定匹配操作符、数据字段变量以及要匹配的正则表达式。

$1 ~ /^data/

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

gawk 'BEGIN{FS=","} $2 ~ /^data2/{print $0}' data1
data21,data22,data23,data24,data25

gawk程序脚本中经常用它在数据文件中搜索特定的数据元素。

gawk -F: '$1 ~ /rich/{print $1,$NF}' /etc/passwd
rich /bin/bash

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

$1 !~ /expression/
gawk -F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd
root /bin/bash
daemon /bin/sh
bin /bin/sh
sys /bin/sh
03. 数学表达式

举个例子,如果你想显示所有属于root用户组(组ID为0)的系统用户,可以用这个脚本。

gawk -F: '$4 == 0{print $1}' /etc/passwd
root
sync
shutdown
halt
operator

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

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

也可以对文本数据使用表达式。

gawk -F, '$1 == "data11"{print $1}' data1
data11

04. 结构化命令

01. if 语句

gawk编程语言支持标准的if-then-else格式的if语句。

if (condition)
statement1
或
if (condition) statement1
gawk '{if ($1 > 20) print $1}' data4
50
34
gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> }
> }' data4
100
68
gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> } else
> {
> x = $1 / 2
> print x
> }}' data4
5
2.5
6.5
100
68

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

if (condition) statement1; else statement2
gawk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' data4
5
2.5
6.5
100
68
02. while 语句
while (condition)
{
statements
}

如果在计算中必须使用每条记录中的多个数据值,这个功能能帮得上忙。

cat data5
130 120 135
160 113 140
145 170 215

gawk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> i++
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667

gawk编程语言支持在while循环中使用break语句和continue语句,允许你从循环中跳出。

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: 125
The average of the first two data elements is: 136.5
The average of the first two data elements is: 157.5
03. do-while 语句
do
{
statements
} while (condition)

这种格式保证了语句会在条件被求值之前至少执行一次。

gawk '{
> total = 0
> i = 1
> do
> {
> total += $i
> i++
> } while (total < 150)
> print total }' data5
250
160
315
04. for 语句

gawk编程语言支持C风格的for循环。

for( variable assignment; condition; iteration process)
gawk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
> total += $i
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667

05. 格式化打印

printf命令的格式:

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

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

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

%[modifier]control-letter

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

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

还有3种修饰符可以用来进一步控制输出。

  • width:指定了输出字段最小宽度的数字值。如果输出短于这个值, printf会将文本右对齐,并用空格进行填充。如果输出比指定的宽度还要长,则按照实际的长度输出。
  • prec:这是一个数字值,指定了浮点数中小数点后面位数,或者文本字符串中显示的最大字符数。
  • -(减号):指明在向格式化空间中放入数据时采用左对齐而不是右对齐。
gawk 'BEGIN{
> x = 10 * 100
> printf "The answer is: %e\n", x
> }'
The answer is: 1.000000e+03
gawk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938
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

注意,你需要在printf命令的末尾手动添加换行符来生成新行。没添加的话, printf命令会继续在同一行打印后续输出。

gawk 'BEGIN{FS=","} {printf "%s ", $1} END{printf "\n"}' data1
data11 data21 data31

默认情况下,printf命令使用右对齐来将数据放到格式化空间中。要改成左对齐,只需给修饰符加一个减号即可。

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

printf命令在处理浮点值时也非常方便。

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

06. 内建函数

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

01. 数学函数
函 数 			描 述
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) 		为计算随机数指定一个种子值

返回一个0~9(包括0和9)的随机整数值。

x = int(10 * rand())

在使用一些数学函数时要小心,因为gawk语言对于它能够处理的数值有一个限定区间。如果超出了这个区间,就会得到一条错误消息。例,尝试计算e的1000次幂。

gawk还支持一些按位操作数据的函数,在处理二进制数时非常有用。

  • 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的按位异或运算。
02. 字符串函数
函 数 						描 述
asort(s [,d]) 				将数组s按数据元素值排序。索引值会被替换成表示新的排序顺序的连续数字。另外,如果指定了d,则排序后的数组会存储在数组d中
asorti(s [,d]) 				将数组s按索引值排序。生成的数组会将索引值作为数据元素值,用连续数字索引来表明排序顺序。另外如果指定了d,排序后的数组会存储在数组d中
gensub(r, s, h [, t]) 		查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果h是一个以g或G开头的字符串,就用s替换掉匹配的文本。如果h是一个数字,它表示要替换掉第h处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和variables返回一个类似于printf输出的字符串
sub(r, s [,t]) 				在变量$0或目标字符串t中查找正则表达式r的匹配。如果找到了,就用字符串s替换掉第一处匹配
substr(s, i [,n]) 			返回s中从索引值i开始的n个字符组成的子字符串。如果未提供n,则返回s剩下的部分
tolower(s) 					将s中的所有字符转换成小写
toupper(s) 					将s中的所有字符转换成大写
gawk 'BEGIN{x = "testing"; print toupper(x); print length(x) }'
TESTING
7

asort和asorti函数是新加入的gawk函数,允许你基于数据元素值(asort)或索引值(asorti)对数组变量进行排序。

gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> var["m"] = 3
> var["u"] = 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

split函数是将数据字段放到数组中以供进一步处理的好办法。

gawk 'BEGIN{ FS=","}{
> split($0, var)
> print var[1], var[5]
> }' data1
data11 data15
data21 data25
data31 data35
03 . 时间函数
函 数 							描 述
mktime(datespec) 				将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值①
strftime(format[,timestamp])	将当前时间的时间戳或timestamp(如果提供了的话)转化格式化日期(采用shell函数date()的格式)								
systime( ) 						返回当前时间的时间戳

时间函数常用来处理日志文件,而日志文件则常含有需要进行比较的日期。通过将日期的文本表示形式转换成epoch时间(自1970-01-01 00:00:00 UTC到现在的秒数),可以轻松地比较日期。

gawk 'BEGIN{
> date = systime()
> day = strftime("%A, %B %d, %Y", date)
> print day
> }'
Friday, December 26, 2014

07. 自定义函数

01. 定义函数

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

function name([variables])
{
statements
}

函数还能用return语句返回值:值可以是变量,或者是最终能计算出值的算式。

function myrand(limit)
{
return int(limit * rand())
}
02. 使用自定义函数

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

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
03. 创建函数库

首先,你需要创建一个存储所有gawk函数的文件。很遗憾,不能将-f命令行参数和内联gawk脚本放到一起使用,不过可以在同一个命令行中使用多个-f参数。

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

gawk -f funclib -f script4 data2
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
Haley Snell - (313)555-4938
04. 实例

下面的脚本对每队的成绩进行了排序,并计算了总分和平均分。

cat bowling.sh
#!/bin/bash
for team in $(gawk -F, '{print $2}' scores.txt | uniq)
do
gawk -v team=$team 'BEGIN{FS=","; total=0}
{
if ($2==team)
{
total += $3 + $4 + $5;
}
}
END {
avg = total / 6;
print "Total for", team, "is", total, ",the average is",avg
}' scores.txt
done
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值