本部分内容参考自《Linux命令行与shell脚本编程大全 第3版》。
gawk 是一门功能丰富的编程语言,你可以通过它所提供的各种特性来编写高级程序处理数据。如果你在接触 shell 脚本前用过其他编程语言,那么
gawk会让你感到十分亲切。在本篇文章,你将会了解如何使用 gawk 编程语言来编写程序,处理可能遇到的各种数据格式化任务。
gawk 程序使用内建变量来引用程序数据里的一些特殊功能。
内建变量
字段和记录分隔符变量
在上一篇文章中演示了 gawk 中的一种内建变量类型——数据字段变量。数据字段变量允许你使用美元符号($
)和字段在该记录中的位置值来引用记录对应的字段。因此,要引用记录中的第一个数据字段,就用变量 $1
;要引用第二个字段,就用 $2
,依次类推。
数据字段是由字段分隔符来划定的。默认情况下,字段分隔符是一个空白字符,也就是空格符或者制表符。
在前面讲了如何在命令行下使用命令行参数 -F
或者在 gawk
程序中使用特殊的内建变量 FS
来更改字段分隔符。
内建变量 FS
是一组内建变量中的一个,这组变量用于控制 gawk
如何处理输入输出数据中的字段和记录。
下表列出了这些内建变量:
变量 | 描述 |
---|---|
FIELDWIDTHS | 由空格分隔的一列数字,定义了每个数据字段确切宽度 |
FS | 输入字段分隔符 |
RS | 输入记录分隔符 |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
变量 FS
和 OFS
定义了 gawk 如何处理数据流中的数据字段。你已经知道了如何使用变量 FS
来定义记录中的字段分隔符。变量 OFS
具备相同的功能,只不过是用在 print
命令的输出上。
默认情况下, gawk 将 OFS
设成一个空格,所以如果你用命令:
print $1,$2,$3
会看到如下输出:
field1 field2 field3
在下面的例子里,你能看到这点。
$ cat test3.txt
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
$ gawk 'BEGIN{FS=","} {print $1,$2,$3}' test3.txt
data11 data12 data13
data21 data22 data23
data31 data32 data33
print
命令会自动将 OFS
变量的值放置在输出中的每个字段间。 通过设置 OFS
变量,可以在输出中使用任意字符串来分隔字段。
$ gawk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' test3.txt
data11-data12-data13
data21-data22-data23
data31-data32-data33
FIELDWIDTHS
变量允许你不依靠字段分隔符来读取记录。在一些应用程序中,数据并没有使用字段分隔符,而是被放置在了记录中的特定列。这种情况下,必须设定 FIELDWIDTHS
变量来匹配数据在记录中的位置。
一旦设置了 FIELDWIDTH
变量, gawk就会忽略FS变量,并根据提供的字段宽度来计算字段。下面是个采用字段宽度而非字段分隔符的例子。
$ gawk 'BEGIN{FIELDWIDTHS="7 7 7"}{print $1,$2,$3}' test3.txt
data11, data12, data13,
data21, data22, data23,
data31, data32, data33,
FIELDWIDTHS
变量定义了四个字段, gawk 依此来解析数据记录。每个记录中的数字串会根据已定义好的字段长度来分割。
一定要记住,一旦设定了
FIELDWIDTHS
变量的值,就不能再改变了。这种方法并不适用于变长的字段。
变量 RS
和 ORS
定义了 gawk 程序如何处理数据流中的字段。默认情况下, gawk 将 RS 和 ORS 设为换行符。默认的 RS
值表明,输入数据流中的每行新文本就是一条新纪录。
有时,你会在数据流中碰到占据多行的字段。典型的例子是包含地址和电话号码的数据,其中地址和电话号码各占一行。
Riley Mullen
123 Main Street
Chicago, IL 60601
(312)555-1234
如果你用默认的 FS
和 RS
变量值来读取这组数据, gawk 就会把每行作为一条单独的记录来读取,并将记录中的空格当作字段分隔符。这可不是你希望看到的。
要解决这个问题,只需把 FS
变量设置成换行符。这就表明数据流中的每行都是一个单独的字段,每行上的所有数据都属于同一个字段。但现在令你头疼的是无从判断一个新的数据行从何开始。
对于这一问题,可以把 RS
变量设置成空字符串,然后在数据记录间留一个空白行。 gawk 会把每个空白行当作一个记录分隔符。
下面的例子使用了这种方法。
$ cat test4.txt
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}' test4.txt
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938
太好了,现在 gawk 把文件中的每行都当成一个字段,把空白行当作记录分隔符。
数据变量
除了字段和记录分隔符变量外,gawk 还提供了其他一些内建变量来帮助你了解数据发生了什么变化,并提取shell环境的信息。下表列出了 gawk 中的其他内建变量。
变量 | 描述 |
---|---|
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 函数所匹配的子字符串的起始位置 |
你应该能从上面的列表中认出一些 shell 脚本编程中的变量。 ARGC
和 ARGV
变量允许从 shell 中获得命令行参数的总数以及它们的值。但这可能有点麻烦,因为 gawk 并不会将程序脚本当成命令行参数的一部分。
$ gawk 'BEGIN{print ARGC,ARGV[0],ARGV[1]}' test4.txt
2 gawk test4.txt
ARGC
变量表明命令行上有两个参数。这包括 gawk
命令和 test4.txt
参数(记住,程序脚本并不算参数)。ARGV
数组从索引 0
开始,代表的是命令。索引值为 1
的数组值是 gawk
命令后的第一个命令行参数。
跟 shell 变量不同,在脚本中引用 gawk 变量时,变量名前不加美元符。
ENVIRON
变量看起来可能有点陌生。它使用关联数组来提取 shell 环境变量。关联数组用文本作为数组的索引值,而不是数值。
数组索引中的文本是 shell 环境变量名,而数组的值则是 shell 环境变量的值。下面有个例子。
$ gawk '
> BEGIN{
> print ENVIRON["HOME"]
> print ENVIRON["PATH"]
> }'
/root
/scripts/shell/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
ENVIRON["HOME"]
变量从 shell 中提取了 HOME
环境变量的值。类似地,ENVIRON["PATH"]
提取了 PATH
环境变量的值。可以用这种方法来从 shell 中提取任何环境变量的值,以供 gawk 程序使用。
当要在 gawk 程序中跟踪数据字段和记录时,变量 FNR
、NF
和 NR
用起来就非常方便。有时你并不知道记录中到底有多少个数据字段。NF
变量可以让你在不知道具体位置的情况下指定记录中的最后一个数据字段。
$ gawk 'BEGIN{FS=":"; OFS=":"} {print $1,$NF}' /etc/passwd
root:/bin/bash
bin:/sbin/nologin
daemon:/sbin/nologin
adm:/sbin/nologin
lp:/sbin/nologin
...
NF
变量含有数据文件中最后一个数据字段的数字值。可以在它前面加个美元符将其用作字段变量。
FNR
和 NR
变量虽然类似,但又略有不同。 FNR
变量含有当前数据文件中已处理过的记录数,NR
变量则含有已处理过的记录总数。让我们看几个例子来了解一下这个差别。
$ gawk 'BEGIN{FS=","}{print $1,"FNR="FNR}' test3.txt test3.txt
data11 FNR=1
data21 FNR=2
data31 FNR=3
data11 FNR=1
data21 FNR=2
data31 FNR=3
在这个例子中, gawk 程序的命令行定义了两个输入文件(两次指定的是同样的输入文件)。这个脚本会打印第一个数据字段的值和 FNR
变量的当前值。注意,当 gawk 程序处理第二个数据文件时,FNR
值被设回了 1
。
现在,让我们加上 NR
变量看看会输出什么。
$ gawk 'BEGIN{FS=","}{print $1,"NR="NR}' test3.txt test3.txt
data11 NR=1
data21 NR=2
data31 NR=3
data11 NR=4
data21 NR=5
data31 NR=6
FNR
变量的值在 gawk 处理第二个数据文件时被重置了,而 NR
变量则在处理第二个数据文件时继续计数。结果就是:如果只使用一个数据文件作为输入,FNR
和 NR
的值是相同的;如果使用多个数据文件作为输入,FNR
的值会在处理每个数据文件时被重置,而 NR
的值则会继续计数直到处理完所有的数据文件。
自定义变量
跟其他典型的编程语言一样,gawk 允许你定义自己的变量在程序代码中使用。 gawk 自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。重要的是,要记住 gawk 变量名区分大小写。
在脚本中给变量赋值
在 gawk 程序中给变量赋值跟在 shell 脚本中赋值类似,都用赋值语句。
$ gawk 'BEGIN{
> var1="test Str";
> print var1;
> }'
test Str
print
语句的输出是 var1
变量的当前值。跟 shell 脚本变量一样,gawk 变量可以保存数值或文本值。
赋值语句还可以包含数学算式来处理数字值。
$ gawk 'BEGIN{x=4; x= x * 2 + 3; print x}'
11
如你在这个例子中看到的, gawk 编程语言包含了用来处理数字值的标准算数操作符。其中包括求余符号(%
)和幂运算符号(^
或 **
)。
在命令行上给变量赋值
也可以用 gawk 命令行来给程序中的变量赋值。这允许你在正常的代码之外赋值,即时改变变量的值。下面的例子使用命令行变量来显示文件中特定数据字段。
$ cat script3.awk
BEGIN{FS=","}
{print $n}
$ gawk -f script3.awk n=2 test3.txt
data12
data22
data32
$ gawk -f script3.awk n=5 test3.txt
data15
data25
data35
这个特性可以让你在不改变脚本代码的情况下就能够改变脚本的行为。第一个例子显示了文件的第二个数据字段,第二个例子显示了第五个数据字段,只要在命令行上设置 n
变量的值就行。
使用命令行参数来定义变量值会有一个问题。在你设置了变量后,这个值在代码的 BEGIN
部分不可用。
$ cat script4.awk
BEGIN{print "The starting value is",n; FS=","}
{print $n}
$ gawk -f script4.awk n=3 test3.txt
The starting value is
data13
data23
data33
可以用 -v
命令行参数来解决这个问题。它允许你在 BEGIN
代码之前设定变量。
$ gawk -f script4.awk -v n=3 test3.txt
The starting value is 3
data13
data23
data33
现在在 BEGIN
代码部分中的变量 n
的值已经是命令行上设定的那个值了。
介绍
awk
是一个强大的文本分析工具,相对于 grep
的查找,sed
的编辑,awk
在其对数据分析并生成报告时,显得尤为强大。简单来说 awk
就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。
gawk
程序是 Unix 中原 awk
程序的 GNU 版本。现在我们平常使用的 awk
其实就是 gawk
,可以看一下 awk
命令存放位置,awk
建立一个软连接指向 gawk
,所以在系统中你使用 awk
或是 gawk
都一样的。
$ ll /bin/awk
lrwxrwxrwx. 1 root root 4 Dec 3 20:48 /bin/awk -> gawk
使用
基本语法
gawk [options] 'program' FILE ...
program:PATTERN{ACTION STATEMENTS}
PATTERN:地址定界;
empty:默认不指定时使用的模式,处理每一行;
/regular expression/:仅处理被正则表达式匹配到的行,取反在模式前加 ! 即可;
relational expression:关系型表达式,结果有真有假(非零值或非空串都表示真),为真时才被处理;
ACTION STATEMENTS:定义对匹配到的行做的动作块;
option:常用选项
-F:指明输入时使用的字段分隔符;
-v var=value:自定义变量;
内建命令
print item1,item2,...
要点:
- 使用逗号作为分隔符;
- 输出的各
item
可以是字符串,也可以是数值、当前记录的字段、变量或 awk 的表达式; - 变量需要放在引号之外才会被解析;
- 如省略
item
,相当于print $0
即输出整行;
printf
printf
可用来格式化输出字符串,使用格式如下:
printf FORMAT, item1, item2, ...
FORMAT:格式化字符串模板;
可用的格式符如下:
%c:显示字符的 ASCII 码;
%d, %i:显示十进制整数;
%e, %E:科学计数法数值显示;
%f:显示为浮点数;
%g, %G:以科学计数法或浮点形式显示数值;
%s:显示字符串;
%u:无符号整数;
%%:显示 % 自身;
#[.#]:修饰符,如 %10f 表示固定宽度为 10 显示字符串;
第一个 # 表示控制显示的宽度,第二个 # 表示小数点后的精度;
-:表示左对齐;+:显示数值的符号,如 %-10f 表示以左对齐的方式固定宽度为 10 显示字符串;
注意:
FORMAT
必须给出;- 不会自动换行,需要显式给出换行控制符
\n
; FORMAT
中需要分别为后面的每个item
指定一个格式化符号;
变量
内建变量
FS
:输入时字段分隔符,默认为空白字符;OFS
:输出时字段分隔符,默认为空白字符;RS
:输入时的换行符;ORS
:输出时的换行符;NF
:每一行的字段数量,$NF
表示最后一个字段;NR
:行数,多个文件时会累加;FNR
:行号,每个文件会单独计数;FILENAME
:当前文件名称;ARGC
:命令行参数的个数,包含awk
命令自身的计数;ARGV
:保存了命令行中给定的各参数的数组,包含awk
命令自身;
自定义变量
awk 中要使用自定义变量有两种方式:
- 直接在命令行通过
-v var=value
定义即可; - 在 program 中直接以一个赋值语句格式定义;
注意:变量名区分大小写。
示例
例 1:显示 /etc/fstab
中后 3 行的第 2 列和第 4 列。
$ tail -3 /etc/fstab | awk '{print $2,$4}'
/ defaults
/boot defaults
swap defaults
例 2:显示 /etc/passwd
中用户名、UID 和默认的 Shell,并在输出是以 :
连接各字段。
$ cat /etc/passwd | awk -F: -v OFS=: '{print $1,$3,$7}'
评论区