深入探索 awk 语言与扩展:从 API 到语言进化
一、API 测试与 gawkextlib 项目
在 awk 编程中,API 测试和相关扩展项目起着至关重要的作用。
testext
扩展对扩展 API 中未被其他示例测试的部分进行了测试。
extension/testext.c
文件既包含了扩展的 C 代码,又在 C 注释中包含了用于运行测试的 awk 测试代码。测试框架会提取这些 awk 代码并执行测试。
gawkextlib
项目提供了许多 gawk 扩展,其中包括用于处理 XML 文件的扩展,它是原始
xgawk
(XML gawk)项目的演进。截至目前,该项目有以下七种扩展:
- errno 扩展
- GD 图形库扩展
- PDF 扩展
- PostgreSQL 扩展
- MPFR 库扩展(提供了一些 gawk 原生 MPFR 支持未涵盖的 MPFR 函数)
- Redis 扩展
- XML 解析器扩展(使用 Expat XML 解析库)
要获取
gawkextlib
项目的代码,可以使用 Git 分布式源代码控制系统,命令如下:
git clone git://git.code.sf.net/p/gawkextlib/code gawkextlib-code
若要构建和使用 XML 扩展,需要安装 Expat XML 解析库,同时还必须安装 GNU Autotools(包括 Autoconf、Automake、Libtool 和 GNU gettext)。
构建和测试
gawkextlib
的简单步骤如下:
1.
构建并安装 gawk
:
cd .../path/to/gawk/code
./configure --prefix=/tmp/newgawk # 目前安装到 /tmp/newgawk
make && make check # 构建并检查是否一切正常
make install # 安装 gawk
- 构建并测试 gawkextlib :
cd .../path/to/gawkextlib-code
./update-autotools # 生成 configure 等文件
# 可能需要运行此命令两次
./configure --with-gawk=/tmp/newgawk # 配置,指向已安装的 gawk
make && make check # 构建并检查是否一切正常
make install # 安装扩展
如果以标准方式安装了 gawk,在配置
gawkextlib
时可能不需要
--with-gawk
选项。根据系统情况,可能需要使用
sudo
工具来安装 gawk 和
gawkextlib
。
二、编写 gawk 扩展的要点
可以使用 gawk 开发者定义的应用程序编程接口(API),用 C 或 C++ 为 gawk 编写扩展(有时也称为插件)。扩展必须拥有与 GNU 通用公共许可证(GPL)兼容的许可证,并通过声明一个名为
plugin_is_GPL_compatible
的变量来表明这一事实。
gawk 与扩展之间的通信是双向的。gawk 会向扩展传递一个包含各种数据字段和函数指针的结构体,扩展可以通过这些函数指针调用 gawk 来完成特定任务。其中一个任务是向 gawk “注册” 新的 awk 级函数的名称和实现,实现形式为具有特定签名的 C 函数指针,按照惯例,实现函数通常命名为
do_XXXX()
,对应某个 awk 级函数
XXXX()
。
API 在名为
gawkapi.h
的头文件中定义,在源文件中包含该头文件之前,必须包含一些标准头文件。API 提供了以下几类操作的函数指针:
- 内存分配、重新分配和释放
- 注册函数(可注册扩展函数、退出回调、版本字符串、输入解析器、输出包装器和双向处理器)
- 打印致命、警告和 “lint” 警告消息
- 更新或取消设置 ERRNO
- 访问参数,包括将未定义的参数转换为数组
- 符号表访问(检索、创建或更改全局变量)
- 创建和释放缓存值,这为多个变量使用值提供了一种高效的方式,可显著提高性能
- 数组操作(检索、添加、删除和修改元素;获取数组元素数量;创建新数组;清空数组;扁平化数组以便于使用 C 风格遍历所有索引和元素)
API 定义了一些标准数据类型,用于表示 awk 值、数组元素和数组。它还提供了方便的函数来构造值,以及内存管理函数,以确保 gawk 分配的内存和扩展分配的内存之间的兼容性。扩展必须将 gawk 传递的所有内存视为只读,而扩展传递给 gawk 的所有内存必须来自 API 的内存分配函数,gawk 会在适当的时候负责释放这些内存。API 还提供了有关运行中的 gawk 版本的信息,以便扩展确保与加载它的 gawk 兼容。
开始编写新扩展时,最简单的方法是复制相关的样板代码,
gawkapi.h
头文件中的宏使这一过程更加容易。gawk 发行版包含了一些小而有用的示例扩展,
gawkextlib
项目则包含了更多(更大)的扩展。如果想编写扩展并与其他 gawk 用户分享,
gawkextlib
项目是一个不错的选择。
三、awk 语言的进化历程
awk 语言有着丰富的进化历程,许多长期使用 awk 的用户最初是在 Version 7 Unix 中学习 awk 编程的。以下是不同版本之间的主要变化:
1. V7 到 SVR3.1 的主要变化
在 Version 7 Unix(1978 年)发布到 System V Release 3.1(1987 年)首次广泛可用的新版本之间,awk 语言发生了显著的演变,主要变化如下:
- 行内规则需要用
;
分隔
- 引入用户定义函数和
return
语句
- 增加
delete
语句
- 引入
do-while
语句
- 新增内置函数
atan2()
、
cos()
、
sin()
、
rand()
和
srand()
- 新增内置函数
gsub()
、
sub()
和
match()
- 新增内置函数
close()
和
system()
- 新增预定义变量
ARGC
、
ARGV
、
FNR
、
RLENGTH
、
RSTART
和
SUBSEP
-
$0
可赋值
- 引入三元运算符
?:
用于条件表达式
- 允许在
for
语句之外使用
indx in array
表达式
- 引入指数运算符
^
及其赋值形式
^=
- 采用与 C 兼容的运算符优先级,这会破坏一些旧的 awk 程序
-
FS
可以是正则表达式,
split()
函数的第三个参数也可以是正则表达式,而不仅仅是
FS
的第一个字符
- 允许
~
和
!~
运算符的操作数为动态正则表达式
- 引入转义序列
\b
、
\f
和
\r
- 支持
getline
函数的输入重定向
- 支持多个
BEGIN
和
END
规则
- 引入多维数组
2. SVR3.1 到 SVR4 的变化
System V Release 4(1989 年)版本的 Unix awk 增加了以下功能(其中一些源自 gawk):
- 新增
ENVIRON
数组
- 命令行支持多个
-f
选项
- 新增
-v
选项,用于在程序执行开始前赋值变量
- 引入
--
信号用于终止命令行选项
- 新增转义序列
\a
、
\v
和
\x
- 为
srand()
内置函数定义了返回值
- 新增用于大小写转换的内置字符串函数
toupper()
和
tolower()
- 对
printf
函数中的
%c
格式控制字符进行了更清晰的规范
- 允许在
printf
和
sprintf()
的参数列表中动态传递字段宽度和精度(
%*.*d
)
- 允许将正则表达式常量(如
/foo/
)用作表达式,等同于使用匹配运算符(如
$0 ~ /foo/
)
- 支持处理命令行变量赋值中的转义序列
3. SVR4 到 POSIX awk 的变化
1992 年的 POSIX 命令语言和实用工具标准为 awk 语言带来了以下变化:
- 使用
-W
作为特定实现的选项
- 使用
CONVFMT
控制数字到字符串的转换
- 引入数字字符串的概念,并采用更严格的比较规则
- 禁止使用预定义变量作为函数参数名
- 对许多以前未记录的语言特性进行了更完整的文档记录
2012 年,许多多年来常用的扩展最终被添加到 POSIX 标准中,包括:
-
fflush()
内置函数,用于刷新缓冲输出
-
nextfile
语句
- 支持使用
delete array
一次性删除整个数组
四、不同版本 awk 的扩展特性
除了标准的 POSIX awk 特性,不同版本的 awk 还有各自的扩展特性。
1. Brian Kernighan 的 awk 扩展
Brian Kernighan 的 awk 版本有以下常见扩展:
- 新增
**
和
**=
运算符
- 可以使用
func
作为
function
的缩写
- 包含
fflush()
内置函数,用于刷新缓冲输出
2. gawk 中未包含在 POSIX awk 中的扩展
GNU 实现的 gawk 增加了大量特性,这些特性可以通过
--traditional
或
--posix
选项禁用。当前版本的 gawk 相对于 POSIX awk 有以下额外特性:
额外的预定义变量
:
-
ARGIND
、
BINMODE
、
ERRNO
、
FIELDWIDTHS
、
FPAT
、
IGNORECASE
、
LINT
、
PROCINFO
、
RT
和
TEXTDOMAIN
变量
I/O 重定向中的特殊文件
:
-
/dev/stdin
、
/dev/stdout
、
/dev/stderr
和
/dev/fd/N
特殊文件名
-
/inet
、
/inet4
和
/inet6
特殊文件,用于 TCP/IP 网络编程,使用
|&
指定要使用的 IP 协议版本
语言的更改和添加
:
- 支持
\x
转义序列
- 全面支持 POSIX 和 GNU 正则表达式
-
FS
和
split()
函数的第三个参数可以为空字符串
-
RS
可以是正则表达式
- 允许在 awk 程序源代码中使用八进制和十六进制常量
- 引入
|&
运算符,用于与协进程进行双向 I/O
- 支持间接函数调用
- 命令行中的目录会产生警告并被跳过
新关键字
:
-
BEGINFILE
和
ENDFILE
特殊模式
-
switch
语句
标准 awk 函数的更改
:
-
close()
函数增加可选的第二个参数,允许关闭与协进程的双向管道的一端
- 使用
--posix
选项时,
gsub()
和
sub()
符合 POSIX 标准
-
length()
函数接受数组参数并返回数组中的元素数量
-
match()
函数增加可选的第三个参数,用于捕获正则表达式中的文本匹配子表达式
-
printf
格式中增加位置说明符,便于翻译
-
split()
函数增加可选的第四个参数,用于保存字段分隔符的文本
仅在 gawk 中可用的额外函数
:
-
gensub()
、
patsplit()
和
strtonum()
函数,用于更强大的文本处理
-
asort()
和
asorti()
函数,用于对数组进行排序
-
mktime()
、
systime()
和
strftime()
函数,用于处理时间戳
-
and()
、
compl()
、
lshift()
、
or()
、
rshift()
和
xor()
函数,用于位操作
-
isarray()
函数,用于检查变量是否为数组
-
bindtextdomain()
、
dcgettext()
和
dcngettext()
函数,用于国际化
命令行选项的更改和添加
:
-
AWKPATH
环境变量,用于为
-f
命令行选项指定路径搜索
-
AWKLIBPATH
环境变量,用于为
-l
命令行选项指定路径搜索
- 新增
-b
、
-c
、
-C
等短选项,以及
--assign
、
--bignum
等长选项
此外,gawk 4.0 版本的代码和文档中移除了对一些过时系统的支持,包括 Amiga、Atari、BeOS 等;gawk 4.1 版本的代码中移除了对 Ultrix 的支持。
五、常见扩展总结
以下表格总结了 gawk、Brian Kernighan 的 awk 和 mawk 这三个最广泛使用的免费可用 awk 版本所支持的常见扩展:
| 特性 | BWK awk | mawk | gawk | 现已成为标准 |
| — | — | — | — | — |
|
\x
转义序列 | ✓ | ✓ | ✓ | |
|
FS
为空字符串 | ✓ | ✓ | ✓ | |
|
/dev/stdin
特殊文件 | ✓ | ✓ | ✓ | |
|
/dev/stdout
特殊文件 | ✓ | ✓ | ✓ | |
|
/dev/stderr
特殊文件 | ✓ | ✓ | ✓ | |
| 不带下标的
delete
| ✓ | ✓ | ✓ | ✓ |
|
fflush()
函数 | ✓ | ✓ | ✓ | ✓ |
| 数组的
length()
| ✓ | ✓ | ✓ | |
|
nextfile
语句 | ✓ | ✓ | ✓ | ✓ |
|
**
和
**=
运算符 | ✓ | | ✓ | |
|
func
关键字 | ✓ | | ✓ | |
|
BINMODE
变量 | | ✓ | ✓ | |
|
RS
为正则表达式 | | ✓ | ✓ | |
| 与时间相关的函数 | | ✓ | ✓ | |
六、正则表达式范围与区域设置的复杂历史
正则表达式中的范围及其与区域设置的交互有着复杂的历史,这对不同版本的 gawk 产生了影响。
最初,处理正则表达式的 Unix 工具将字符范围(如
[a-z]
)定义为匹配范围中第一个字符到最后一个字符之间的任何字符(包括两端),排序基于机器原生字符集中每个字符的数值。因此,在基于 ASCII 的系统中,
[a-z]
只匹配所有小写字母,因为从
a
到
z
的字母数值是连续的。但在 EBCDIC 系统中,
[a-z]
范围还包括其他非字母字符。
几乎所有的 Unix 入门文献都将范围表达式解释为这种工作方式,特别是教导使用
[a-z]
来匹配小写字母,使用
[A-Z]
来匹配大写字母。然而,1992 年的 POSIX 标准引入了区域设置的概念。由于许多区域设置包含除了英语字母表的 26 个字母之外的其他字母,POSIX 标准添加了字符类来匹配 ASCII 字符集之外的不同类型的字符。
同时,标准改变了范围表达式的解释。在 “C” 和 “POSIX” 区域设置中,像
[a-dx-z]
这样的范围表达式仍然等同于
[abcdxyz]
,就像在 ASCII 中一样。但在其他区域设置中,排序是基于排序顺序的。例如,在许多区域设置中,
A
和
a
都小于
B
,这些区域设置按字典顺序对字符进行排序,
[a-dx-z]
通常不等同于
[abcdxyz]
,可能等同于
[ABCXYabcdxyz]
。
这一点需要强调:很多文献教导使用
[a-z]
来匹配小写字符,但在非 ASCII 区域设置的系统中,它也会匹配除
A
或
Z
之外的所有大写字符,这在二十一世纪仍然是一个持续造成混淆的原因。
为了说明这些问题,以下示例使用
sub()
函数进行文本替换,目的是去除尾随的大写字符:
$ echo something1234abc | gawk-3.1.8 '{ sub("[A-Z]*$", ""); print }'
something1234a
通过对 awk 语言的 API 测试、扩展项目以及语言进化历程的深入了解,我们可以更好地利用 awk 进行编程,发挥其强大的功能。无论是编写扩展以满足特定需求,还是根据不同版本的特性选择合适的编程方式,都能让我们在数据处理和文本操作中更加得心应手。
深入探索 awk 语言与扩展:从 API 到语言进化
七、awk 编程中的实用操作流程
在实际的 awk 编程中,掌握一些实用的操作流程能够提高开发效率和代码质量。下面将详细介绍几个关键的操作流程。
1. 构建和使用 gawkextlib 扩展的流程图
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(安装 Expat XML 解析库):::process
B --> C(安装 GNU Autotools):::process
C --> D(构建并安装 gawk):::process
D --> E(克隆 gawkextlib 项目代码):::process
E --> F(更新 Autotools):::process
F --> G{是否需要再次更新?}:::decision
G -->|是| F
G -->|否| H(配置 gawkextlib):::process
H --> I(构建并检查 gawkextlib):::process
I --> J(安装 gawkextlib 扩展):::process
J --> K([结束]):::startend
这个流程图清晰地展示了构建和使用 gawkextlib 扩展的详细步骤,从前期的库和工具安装,到代码克隆、配置、构建和最终安装,每个环节都一目了然。
2. 编写 gawk 扩展的步骤列表
编写 gawk 扩展时,可以按照以下步骤进行:
1.
了解 API
:熟悉
gawkapi.h
头文件中定义的 API,包括数据类型、函数指针和操作函数。例如,了解如何使用内存分配函数、注册函数等。
2.
选择合适的语言
:可以使用 C 或 C++ 编写扩展,但要确保代码与 gawk 的 API 兼容。
3.
声明许可证兼容性
:在代码中声明
plugin_is_GPL_compatible
变量,以表明扩展的许可证与 GNU 通用公共许可证(GPL)兼容。
4.
实现扩展功能
:根据需求实现新的 awk 级函数或其他功能,通过 API 与 gawk 进行交互。例如,注册新函数时使用相应的注册函数指针。
5.
进行内存管理
:遵循 API 的内存管理规则,将 gawk 传递的内存视为只读,使用 API 的内存分配函数为 gawk 提供内存。
6.
测试扩展
:使用
testext
扩展类似的方式编写测试代码,确保扩展功能的正确性。
7.
集成到 gawkextlib(可选)
:如果希望与其他 gawk 用户分享扩展,可以考虑将其集成到
gawkextlib
项目中。
八、不同版本 awk 扩展特性的对比分析
不同版本的 awk 扩展特性各有优劣,在选择使用时需要根据具体需求进行权衡。下面对 Brian Kernighan 的 awk 和 gawk 的扩展特性进行对比分析。
1. 运算符和关键字扩展
| 特性 | Brian Kernighan 的 awk | gawk |
|---|---|---|
**
和
**=
运算符
| 支持 | 支持 |
func
关键字
| 支持 | 支持 |
这两个版本都支持
**
和
**=
运算符以及
func
关键字,这些扩展为编程提供了更简洁的语法。例如,使用
**
运算符可以更方便地进行幂运算。
2. 文件和输入输出扩展
| 特性 | Brian Kernighan 的 awk | gawk |
|---|---|---|
/dev/stdin
等特殊文件
| 支持 | 支持 |
/inet
等网络特殊文件
| 不支持 | 支持 |
在文件和输入输出方面,两者都支持
/dev/stdin
、
/dev/stdout
和
/dev/stderr
等特殊文件。但 gawk 还支持
/inet
、
/inet4
和
/inet6
等网络特殊文件,可用于 TCP/IP 网络编程,这是 gawk 在网络编程方面的优势。
3. 函数扩展
| 特性 | Brian Kernighan 的 awk | gawk |
|---|---|---|
fflush()
函数
| 支持 | 支持 |
gensub()
等文本处理函数
| 不支持 | 支持 |
asort()
等数组排序函数
| 不支持 | 支持 |
mktime()
等时间函数
| 不支持 | 支持 |
| 位操作函数 | 不支持 | 支持 |
| 国际化函数 | 不支持 | 支持 |
在函数扩展方面,Brian Kernighan 的 awk 仅支持
fflush()
函数,而 gawk 提供了丰富的函数扩展,包括文本处理、数组排序、时间处理、位操作和国际化等方面的函数,能够满足更复杂的编程需求。
九、正则表达式范围在不同场景下的应用
正则表达式范围在不同的区域设置和编程场景下有不同的应用,需要根据实际情况进行选择和处理。
1. 在 ASCII 区域设置下的应用
在基于 ASCII 的系统和 “C” 或 “POSIX” 区域设置中,正则表达式范围的行为符合传统理解。例如,使用
[a-z]
可以准确匹配所有小写字母,
[A-Z]
可以匹配所有大写字母。以下是一个简单的示例,用于统计文本中小写字母的数量:
#!/usr/bin/awk -f
{
count = 0
for (i = 1; i <= length($0); i++) {
if (substr($0, i, 1) ~ /[a-z]/) {
count++
}
}
print "小写字母数量: " count
}
2. 在非 ASCII 区域设置下的处理
在非 ASCII 区域设置中,由于排序顺序的变化,正则表达式范围的行为可能与预期不同。为了避免混淆,建议使用字符类来匹配特定类型的字符。例如,使用
[[:lower:]]
来匹配小写字母,
[[:upper:]]
来匹配大写字母。以下是一个修改后的示例,在不同区域设置下都能正确统计小写字母的数量:
#!/usr/bin/awk -f
{
count = 0
for (i = 1; i <= length($0); i++) {
if (substr($0, i, 1) ~ /[[:lower:]]/) {
count++
}
}
print "小写字母数量: " count
}
十、总结与建议
通过对 awk 语言的 API 测试、扩展项目、语言进化历程、不同版本扩展特性以及正则表达式范围等方面的深入探讨,我们对 awk 有了更全面的了解。以下是一些总结和建议:
-
扩展开发
:如果需要为 gawk 开发扩展,可以利用
gawkextlib项目提供的资源和框架。遵循 API 的规范和内存管理规则,确保扩展的兼容性和稳定性。同时,编写详细的测试代码,保证扩展功能的正确性。 -
版本选择
:根据具体的编程需求选择合适的 awk 版本。如果需要使用丰富的扩展特性和网络编程功能,建议选择 gawk;如果对语法简洁性有要求,Brian Kernighan 的 awk 中的
func关键字等扩展可能会满足需求。 - 正则表达式使用 :在使用正则表达式范围时,要注意区域设置的影响。在非 ASCII 区域设置中,优先使用字符类来匹配特定类型的字符,避免因范围表达式的解释变化而导致的错误。
总之,掌握 awk 语言的各种特性和扩展,能够让我们在数据处理和文本操作中更加灵活和高效。不断学习和实践,将有助于我们充分发挥 awk 的强大功能。
超级会员免费看
64

被折叠的 条评论
为什么被折叠?



