Linux shell - gawk
gawk 编辑器
虽然 sed 编辑器非常方便,可以即时修改文本文件,但其自身也存在一些局限。你往往还需要一款更高级的文本文件处理工具,这种工具能够提供一个更贴近编程的环境,修改和重新组织文件中的数据。这正是 gawk 大展身手之地。
gawk 是 Unix 中最初的 awk 的 GNU 版本。gawk 比 sed 的流编辑提升了一个“段位”,它提供了一种编程语言,而不仅仅是编辑器命令。在 gawk 编程语言中,可以实现以下操作。
- 定义变量来保存数据。
- 使用算术和字符串运算符来处理数据。
- 使用结构化编程概念(比如 if-then 语句和循环)为数据处理添加处理逻辑。
- 提取文件中的数据将其重新排列组合,最后生成格式化报告。
gawk 的报告生成能力多用于从大文本文件中提取数据并将其格式化成可读性报告。最完美的应用案例是格式化日志文件。在日志文件中找出错误行可不是一件容易事。gawk 能够从日志文件中过滤出所需的数据,将其格式化,以便让重要的数据更易于阅读。
gawk 命令格式
gawk 的基本格式如下。
gawk options program file
选项 | 描述 |
---|---|
-F fs | 指定行中划分数据字段分隔符 |
-f file | 从指定文件中读取 gawk 脚本代码 |
-v var=value | 定义 gawk 脚本中的变量及其默认值 |
-L [keyword] | 指定 gawk 的兼容模式或警告级别 |
gawk 的强大之处在于脚本。你可以编写脚本来读取文本行中的数据,然后对其进行处理并显示,形成各种输出报告。
从命令行读取 gawk 脚本
gawk 脚本用一对花括号来定义。必须将脚本命令放到一对花括号({})之间。如果误把 gawk 脚本放在了圆括号内,就会得到一条类似于下面的错误消息:
gawk '(print "Hello World!")'
# gawk: cmd. line:1: (print "Hello World!")
# gawk: cmd. line:1: ^ syntax error
# gawk: cmd. line:2: (print "Hello World!")
# gawk: cmd. line:2: ^ unexpected newline or end of string
由于 gawk 命令行假定脚本是单个文本字符串,因此还必须将脚本放到单引号中。下面的例子在命令行中指定了一个简单的 gawk 程序脚本:
gawk '{print "Hello World!"}'
这个脚本定义了一个命令:print。该命令名副其实:它会将文本打印到 STDOUT。如果运行这个命令,你可能会有些失望,因为什么都不会发生。由于没有在命令行中指定文件名,因此 gawk 程序会从 STDIN 接收数据。在脚本运行时,它会一直等待来自 STDIN 的文本。
如果你输入一行文本并按下 Enter 键,则 gawk 会对这行文本执行一遍脚本。和 sed 编辑器一样,gawk 会对数据流中的每一行文本都执行脚本。由于脚本被设为显示一行固定的文本字符串,因此不管在数据流中输入什么文本,你都会得到同样的文本输出:
gawk '{print "Hello World!"}'
This is a test
# Hello World!
hello
# Hello World!
Goodbye
# Hello World!
This is another test
# Hello World!
要终止这个 gawk 程序,必须表明数据流已经结束了。bash shell 提供了 Ctrl+D 组合键来生成 EOF(end-of-file)字符。使用该组合键可以终止 gawk 程序并返回到命令行界面。
使用数据字段变量
gawk 的主要特性之一是处理文本文件中的数据。它会自动为每一行的各个数据元素分配一个变量。在默认情况下,gawk 会将下列变量分配给文本行中的数据字段。
- $0 代表整个文本行。
- $1 代表文本行中的第一个数据字段。
- $2 代表文本行中的第二个数据字段。
- $n 代表文本行中的第 n 个数据字段。
文本行中的数据字段是通过字段分隔符来划分的。在读取一行文本时,gawk 会用预先定义好的字段分隔符划分出各个数据字段。在默认情况下,字段分隔符是任意的空白字符(比如空格或制表符)。
在下面的例子中,gawk 脚本会读取文本文件,只显示第一个数据字段的值:
cat data2.txt
# One line of test text.
# Two lines of test text.
# Three lines of test text.
gawk '{print $1}' data2.txt
# One
# Two
# Three
该脚本使用$1 字段变量来显示每行文本的第一个数据字段。
如果要读取的文件采用了其他的字段分隔符,可以通过-F 选项指定:
gawk -F: '{print $1}' /etc/passwd
# root
# daemon
# bin
# [...]
# roger
# sshd
这个简短的脚本显示了系统中密码文件的第一个数据字段。由于/etc/passwd 文件使用冒号(:)来分隔数据字段,因此要想划出数据字段,就必须在 gawk 选项中将冒号指定为字段分隔符(-F:)。
在脚本中使用多条命令
如果一种编程语言只能执行一条命令,那也没多大用处。gawk 编程语言允许将多条命令组合成一个常规的脚本。要在命令行指定的脚本中使用多条命令,只需在命令之间加入分号即可:
echo "My name is roger" | gawk '{$4="luojia"; print $0}'
# My name is luojia
第一条命令会为字段变量$4 赋值。第二条命令会打印整个文本行。注意, gawk 在输出中已经将原文本中的第四个数据字段替换成了新值。
也可以用次提示符一次一行地输入脚本命令:
gawk '{
> $4="luojia"
> print $0 }'
My name is roger
# My name is luojia
在使用了表示起始的前单引号后,bash shell 会使用次提示符来提示输入更多数据。你可以一次一行地添加命令,直到输入结尾的后单引号。因为没有在命令行中指定文件名,所以 gawk 程序会从 STDIN 中获取数据。当运行这个脚本的时候,它会等着读取来自 STDIN 的文本。要退出的话,只需按下 Ctrl+D 组合键表明数据结束即可。
从文件中读取脚本
跟 sed 编辑器一样,gawk 允许将脚本保存在文件中,然后在命令行中引用脚本:
cat script2.gawk
# { print $1 "'s home directory is " $6 }
gawk -F: -f script2.gawk /etc/passwd
# root's home directory is /root
# daemon's home directory is /usr/sbin
# bin's home directory is /bin
# [...]
# roger's home directory is /home/roger
# sshd's home directory is /run/sshd
script2.gawk 会再次使用 print 命令打印/etc/passwd 文件的主目录数据字段(字段变量$6),以及用户名数据字段(字段变量$1)。
可以在脚本文件中指定多条命令。为此,只需一行写一条命令即可,且无须加分号:
cat script3.gawk
# {
# text = "'s home directory is "
# print $1 text $6
# }
gawk -F: -f script3.gawk /etc/passwd
# root's home directory is /root
# daemon's home directory is /usr/sbin
# bin's home directory is /bin
# [...]
# roger's home directory is /home/roger
# sshd's home directory is /run/sshd
script3.gawk 脚本定义了变量 text 来保存 print 命令中用到的文本字符串。注意,在 gawk 脚本中,引用变量值时无须像 shell 脚本那样使用美元符号。
在处理数据前运行脚本
gawk 还允许指定脚本何时运行。在默认情况下,gawk 会从输入中读取一行文本,然后对这一行的数据执行脚本。但有时候,可能需要在处理数据前先运行脚本,比如要为报告创建一个标题。BEGIN 关键字就是用来做这个的。它会强制 gawk 在读取数据前执行 BEGIN 关键字之后指定的脚本:
gawk 'BEGIN {print "Hello World!"}'
# Hello World!
这次 print 命令会在读取数据前显示文本。但在显示过文本后,脚本就直接结束了,不等待任何数据。
原因在于 BEGIN 关键字在处理任何数据之前仅应用指定的脚本。如果想使用正常的脚本来处理数据,则必须用另一个区域来定义脚本:
cat data3.txt
# Line 1
# Line 2
# Line 3
gawk 'BEGIN { print "The data3 File Contents:" }
> { print $0 }' data3.txt
# The data3 File Contents:
# Line 1
# Line 2
# Line 3
现在,在 gawk 执行了 BEGIN 脚本后,会用第二段脚本来处理文件数据。这么做时要小心,因为这两段脚本仍会被视为 gawk 命令行中的一个文本字符串,所以需要相应地加上单引号。
在处理数据后运行脚本
和 BEGIN 关键字类似,END 关键字允许指定一段脚本,gawk 会在处理完数据后执行这段脚本:
gawk 'BEGIN { print "The data3 File Contents:" }
> { print $0 }
> END { print "End of File" }' data3.txt
# The data3 File Contents:
# Line 1
# Line 2
# Line 3
# End of File
gawk 脚本在打印完文件内容后,会执行 END 脚本中的命令。这是在处理完所有正常数据后给报告添加页脚的最佳方法。
可以将各个部分放到一起,组成一个漂亮的小型脚本文件,用它从一个简单的数据文件中创建一份完整的报告:
cat script4.gawk
# BEGIN {
# print "The latest list of users and shells"
# print "UserID \t Shell"
# print "------- \t -------"
# FS=":"
# }
# {
# print $1 " \t " $7
# }
# END {
# print "This concludes the listing"
# }
其中,BEGIN 脚本用于为报告创建标题。另外还定义了一个殊变量 FS。这是定义字段分隔符的另一种方法。这样就无须依靠脚本用户通过命令行选项定义字段分隔符了。
下面是这个 gawk 脚本的输出(有部分删节):
gawk -f script4.gawk /etc/passwd
# The latest list of users and shells
# UserID Shell
# -------- -------
# root /bin/bash
# daemon /usr/sbin/nologin
# [...]
# roger /bin/bash
# sshd /usr/sbin/nologin
# This concludes the listing
和预想的一样,BEGIN 脚本创建了标题,主体脚本处理了特定数据文件(/etc/passwd)中的信息,END 脚本生成了页脚。print 命令中的\t 负责生成美观的选项卡式输出(tabbed output)。