Linux awk总结(一)

虽然sed编辑器是非常方便自动修改文本文件的工具,但其也有自身的限制。通常你需要一个用来处理文本文件中的数据的更高级工具,它能提供一个类编程环境来修改和重新组织文件中的数据,而这正式awk能够做到的。awk让流编辑迈上了一个新的台阶,它提供了一种编程语言而不只是编辑器命令。在awk中,你可以做下面的事情(awk使用的是gawk版本,相对于原版awk功能更为强大,文中所有的awk均可替换为gawk):

  • 定义变量来保存数据;
  • 使用算数和字符串操作符来处理数据;
  • 使用结构化的编程概念(比如if-else语句和循环)来为数据处理增加处理逻辑;
  • 通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告。

1. awk的命令格式

awk程序的基本格式如下:

awk options program file

可用的选项如下:

  1. -F fs   指定行中划分数据字段的字段分隔符
  2. -f programFile   从指定的文件中读取代码数据
  3. -v var=value   定义awk程序中的一个变量及其默认值

2. 从命令行读取程序脚本

awk程序脚本用一对花括号来定义,必须将脚本命令放在两个花括号({})中。此外,由于awk假定程序脚本是单个文本字符串,因此必须还要将脚本放到单引号中。下面的例子在命令行上指定了一个简单的Hello World例子:

$ awk '{print "Hello World!"}'

这个脚本会将Hello World!打印到STDOUT。如果尝试运行这个命令,你可能会有些失望,因为什么都不会发生,而当你输入任意一行文本并回车之后就会显示。所以awk和sed类似,如果没有指定数据文件,就会默认从STDIN接收数据,在运行这个脚本时会一直等待从SDTIN输入的文本,并对输入的每一行文本执行程序脚本。在向STDIN中输入文件结束符(EOF,Ctrl+D组合键)后,脚本会停止运行。

3. 使用数字字段变量

awk会自动给每一行中的每个元素分配一个变量,在默认情况下,会按照下面的方式分配:

  • $0代表整个文本行
  • $1代表文本行中的第一个数据字段,$2代表第二个字段,以此类推

在文本行中,每个数据字段都是通过字段分隔符划分的。awk中默认的字段分隔符是任意的空白字符(例如空格或制表符tab)。例如:

$ cat test.txt
This is a test, this is the first line of the test.
This is a test, this is the second line of the test.
Hahahaha!
$ awk '{print $1}' test.txt
This
This
Hahahaha!

如果你要读取采用了其他字段分隔符的文件,可以用-F选项指定:

$ awk -F: '{print $1}' /etc/passwd
nobody
root
daemon
_uucp
_taskgated
_networkd
_installassistant
_lp
下面省略...

由于/etc/passwd文件是使用冒号来分离字段的,因此要在awk选项中将冒号指定为字段分隔符。

4. 在程序脚本中使用多个命令

有两种方法:可以在命令之间加个分号,也可以一行输入一个程序脚本命令:

$ echo "My name is oldmanw" | awk '{$4="handsomeBoy"; print $0}'
My name is handsomeBoy
$ echo "My name is oldmanw" | awk '{$4="handsomeBoy"
> print $0}'
My name is handsomeBoy

在使用了表示起始的单引号之后,bash shell会使用次提示符(>)提示你输入更多的数据。你可以在每一行输入一条命令,知道输入了结尾的单引号。

5. 从文件中读取程序

和sed一样,awk允许将程序存储到文件中,然后在命令行中引用,使用-f选项即可。

$ cat awkScript
{print "The meaning of user " $1 " is " $5}
$ awk -F: -f awkScript /etc/passwd
The meaning of user nobody is Unprivileged User
The meaning of user root is System Administrator
The meaning of user daemon is System Services
The meaning of user _uucp is Unix to Unix Copy Protocol
The meaning of user _taskgated is Task Gate Daemon
The meaning of user _networkd is Network Services
The meaning of user _installassistant is Install Assistant
The meaning of user _lp is Printing Services
The meaning of user _postfix is Postfix Mail Server
以下省略...

也可以在程序文件中指定多条命令,只要一行放一条命令即可,不需要使用分号。

6. 在数据处理前后运行脚本

有的时候可能需要在处理数据前运行脚本,如为报告创建标题;或是在处理完数据后运行脚本,在awk中分别使用BEGIN和END关键字实现。

$ awk 'BEGIN {print "Hello World!"}'
Hello World!

这里print命令的结果会直接显示出来,因为BEGIN标示的脚本区域会在读取数据前执行。如果想使用正常的程序脚本处理数据,必须使用另一个脚本区域来定义程序(如果脚本区域过长,可以分成多行输入):

$ awk 'BEGIN {print "Hello World!"} {print $1}' test.txt
Hello World!
This
This
Hahahaha!

END关键字类似,会在处理完数据后执行:

$ awk 'BEGIN {print "Hello World!"} {print $1} END {print "Bye!"}' test.txt
Hello World!
This
This
Hahahaha!
Bye!

7. 使用变量

awk支持两种不同类型的变量:内建变量和自定义变量,下面分别介绍。

(1)内建变量

  1. FIELDWIDTHS   由空格分隔的一系列数字,定义了每个数据字段的确切宽度
  2. FS   输入字段分隔符
  3. RS   输入记录分隔符
  4. OFS   输出字段分隔符
  5. ORS   输出记录分隔符

FS和OFS定义了awk如何处理数据流中的数据字段。FS定义输入流的分隔符,OFS定义了输出流的分隔符,在默认情况下,FS为任意的空白字符,OFS为一个空格。

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

FIELDWIDTHS允许不依赖字段分隔符来读取记录。在一些文本文件中,数据并没有使用字段分隔符,而是被放置在了记录的特定列,这种情况下必须设定FIELDWIDTHS变量来匹配数据在记录中的位置。一旦设置了FIELDWIDTHS变量,awk就会忽略FS变量,并根据提供的字段宽度来计算字段:

$ cat test3.txt
1005.2347596.37
115-2.349194.00
05810.1298100.1
$ awk 'BEGIN {FIELDWIDTHS="3 5 2 5"} {print $1, $2, $3, $4}' test3.txt
100 5.234 75 96.37
115 -2.34 91 94.00
058 10.12 98 100.1

变量RS和ORS定义了awk程序如何让处理数据流中的记录。默认情况下awk将RS和ORS设置为换行符。默认的RS值表明,输入数据流中的每行新文本就是一条新纪录。而有的时候会遇到占据多行的字段,如下所示,每一条数据占据4行,数据之间使用空白行进行隔离:

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

handsomeBoy
No.10 XiTuCheng Road
Beijing
12345678901

如果使用默认的FS和RS变量值来读取这些数据,awk就会把每行作为一条单独的记录来读取,并将记录中的空格当作字段分隔符,但这并不是我们所希望的。因此,对于这个例子,需要把FS设置为换行符(\n),RS设置为空字符串,这样就能够正确的处理数据:

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

除了字段和记录分隔符外,awk还提供了一些其他的内建变量来帮助你了解数据发生了什么变化,并提取shell环境的信息,如下所示:

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

ARGC和ARGV变量允许从shell中获得命令行参数的总数以及它们的值,但是awk并不会将程序脚本当成命令行参数的一部分:

$ awk 'BEGIN {print ARGC, ARGV[1]}' test.txt
2 test.txt
$ awk 'BEGIN {print ARGC, ARGV[0]}' test.txt
2 awk

ARGC变量表明命令行上有两个参数,包括awk和test.txt参数(程序脚本并不算参数)。ARGV数组的索引从0开始,存储参数的名称。

ENVIRON变量使用关联数组来提取shell环境变量,关联数组用文本作为数组的索引值,而不是数值(有点类似于hashmap?):

$ awk 'BEGIN {print ENVIRON["HOME"]; print ENVIRON["PATH"]}'
/Users/oldmanw
/Users/oldmanw/apache-maven-3.5.4/bin:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/bin:/Users/oldmanw/Downloads/phantomjs/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/oldmanw/Documents/gradle-4.10.1/bin:/usr/local/mysql/bin

ENVIRON["HOME"]变量从shell中提取了HOME环境变量的值,ENVIRON["PATH"]变量从shell中提取的PATH环境变量的值。可以用这种方法从shell中提取任何环境变量的值,以供awk使用。

当要在awk程序中跟踪数据字段和记录时,变量FNR,NF和NR用起来就非常方便。有时你并不知道记录中到底有多少个数据字段,NF变量能够让你在不知道具体位置的情况下指定记录中的最后一个数据字段:

$ cat test.txt
This is a test, this is the first line of the test.
This is a test, this is the second line of the test.
Hahahaha!
$ awk '{print $1, $NF}' test.txt
This test.
This test.
Hahahaha! Hahahaha!

FNR和NR变量虽然类似,但又略有不同。FNR变量含有当前数据文件中已处理过的记录数,NR变量含有已处理过的记录数,请看以下例子:

$ cat test2.txt
data11,data12,data13
data21,data22,data23
data31,data32,data33
$ awk 'BEGIN {FS=","} {print $1, "FNR="FNR}' test2.txt test2.txt
data11 FNR=1
data21 FNR=2
data31 FNR=3
data11 FNR=1
data21 FNR=2
data31 FNR=3
$ awk 'BEGIN {FS=","} {print $1, "NR="NR}' test2.txt test2.txt
data11 NR=1
data21 NR=2
data31 NR=3
data11 NR=4
data21 NR=5
data31 NR=6

可以看出,FNR变量在处理第二个文件时被重置了,而NR变量在处理第二个文件时继续计数。因此,如果只是使用一个数据文件作为输入,FNR和NR的值时完全相同的,如果使用多个文件作为输入,FNR的值会在处理每个数据文件时被重置,NR的值会继续计数直到处理完所有的数据文件。

(2)自定义变量

和其他的编程语言一样,awk允许你定义自己的变量在程序代码中使用。awk的自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。重要的是,awk的变量名区分大小写。

在脚本中给变量赋值:

$ awk 'BEGIN {
> x="This is a test"
> print x
> x=123
> print x
> x=x*2+3
> print x
> }'
This is a test
123
249

和shell脚本中类似,在awk中使用赋值语句给变量赋值,变量可以保存数值或文本值,还可以包含数学算式。

此外,也可以使用awk命令行给程序中的变量赋值。

$ cat awkScript2
BEGIN {FS=","}
{print $n}
$ cat test2.txt
data11,data12,data13
data21,data22,data23
data31,data32,data33
$ awk -f awkScript2 n=2 test2.txt
data12
data22
data32
$ awk -f awkScript2 n=3 test2.txt
data13
data23
data33

这个特性能够让你在不改变脚本代码的情况下就能够改变脚本的行为。但是使用命令行参数来定义变量会有一个问题,在设置了变量后,这个值在脚本的BEGIN部分不可用:

$ cat awkScript3
BEGIN {print "The starting value is", n; FS=","}
{print $n}
$ awk -f awkScript3 n=3 test2.txt
The starting value is
data13
data23
data33

解决方法是使用-v选项,它允许你在BEGIN代码段之前设定变量。在命令行上,-v选项必须放在脚本代码之前:

$ awk -v n=3 -f awkScript3 test2.txt
The starting value is 3
data13
data23
data33

由于awk的相关知识很多,因此分成两个部分介绍,在下一部分会介绍awk对数组的处理,匹配模式的使用,数学表达式,结构化命令(if,while,do-while,for),格式化打印,内建函数(数字函数和字符串函数)以及自定义函数的使用。

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

猜你喜欢

转载自blog.csdn.net/oldmanw/article/details/84229249