gawk 扩展编程:API 功能详解与应用实践
1. 打印信息
在扩展中,可以打印不同类型的警告信息。使用这些函数时,必须传入扩展加载时从 gawk 接收到的扩展 ID。以下是相关函数:
-
void fatal(awk_ext_id_t id, const char *format, ...);
:打印一条消息,然后使 gawk 立即退出。
-
void warning(awk_ext_id_t id, const char *format, ...);
:打印一条警告消息。
-
void lintwarn(awk_ext_id_t id, const char *format, ...);
:打印一条 “lint 警告”。通常情况下,这与打印警告消息相同,但如果使用
--lint=fatal
调用 gawk,则 lint 警告将变为致命错误消息。
这些函数类似于 C 语言的
printf()
系列函数,
format
参数是一个包含文字字符和格式代码的字符串。
2. 更新 ERRNO
可以使用以下函数更新
ERRNO
变量:
-
void update_ERRNO_int(int errno_val);
:将
ERRNO
设置为
errno_val
中错误代码对应的字符串。该值应该是
<errno.h>
中定义的错误代码之一,gawk 使用 C 语言的
strerror()
函数将其转换为(可能已翻译)的字符串。
-
void update_ERRNO_string(const char *string);
:将
ERRNO
直接设置为字符串值。gawk 会复制
string
的值。
-
void unset_ERRNO(void);
:取消设置
ERRNO
。
3. 请求值
从 gawk 返回值的所有函数工作方式相同。传入一个
awk_valtype_t
值,以指示期望的值类型。如果实际值与请求的值类型匹配,函数返回
true
,并填充
awk_value_t
结果;否则,函数返回
false
,
val_type
成员指示实际值的类型。可以根据情况打印错误消息或重新发出对实际值类型的请求。具体行为总结如下表:
| 实际值类型 \ 请求值类型 | 字符串 | 数字 | 数组 | 未定义 | 值 cookie |
|---|---|---|---|---|---|
| 字符串 | 字符串 | 若可转换为数字则为数字,否则为 false | false | false | false |
| 数字 | 若可转换为字符串则为字符串,否则为 false | 数字 | false | false | false |
| 数组 | false | false | 数组 | false | false |
| 未定义 | 字符串 | 数字 | 数组 | 未定义 | false |
4. 访问和更新参数
有两个函数可用于访问传递给扩展函数的参数:
-
awk_bool_t get_argument(size_t count, awk_valtype_t wanted, awk_value_t *result);
:将第
count
个参数填充到
result
指向的
awk_value_t
结构中。如果实际类型与
wanted
匹配,返回
true
;否则返回
false
。在后者情况下,
result->val_type
指示实际类型。计数从 0 开始,
wanted
指示期望的值类型。
-
awk_bool_t set_argument(size_t count, awk_array_t array);
:将未定义的参数转换为数组,提供数组的引用调用。如果
count
太大,或者参数类型不是未定义,返回
false
。
5. 符号表访问
有两组例程可用于访问全局变量,还有一组可用于创建和释放缓存值。
-
按名称访问和更新变量
-
awk_bool_t sym_lookup(const char *name, awk_valtype_t wanted, awk_value_t *result);
:将
name
字符串命名的变量的值填充到
result
指向的
awk_value_t
结构中。
wanted
指示期望的值类型。如果实际类型与
wanted
匹配,返回
true
;否则返回
false
。
-
awk_bool_t sym_update(const char *name, awk_value_t *value);
:更新
name
字符串命名的变量。如果变量不在 gawk 的符号表中,则将其添加进去。如果一切正常,返回
true
;否则返回
false
。
注意,不允许更改现有变量的类型(从标量到数组或反之),也不能使用此例程更新数组或任何预定义变量(如
ARGC
或
NF
)。除了
PROCINFO
数组外,扩展不能更改 gawk 的特殊变量。如果正在运行的 awk 程序未引用
PROCINFO
,则对其查找可能会失败。
-
按 cookie 访问和更新变量
-
awk_bool_t sym_lookup_scalar(awk_scalar_t cookie, awk_valtype_t wanted, awk_value_t *result);:检索标量 cookie 的当前值。使用sym_lookup()获取标量 cookie 后,可以使用此函数更高效地获取其值。如果无法检索值,返回false。 -
awk_bool_t sym_update_scalar(awk_scalar_t cookie, awk_value_t *value);:更新与标量 cookie 关联的值。如果新值不是AWK_STRING或AWK_NUMBER类型,返回false。同样,不能更新预定义变量。
-
使用标量 cookie 可以避免每次访问变量时都在 gawk 的符号表中进行查找,提高效率。以下是使用示例:
/* do_magic --- do something really great */
static awk_value_t *
do_magic(int nargs, awk_value_t *result)
{
awk_value_t value;
if ( sym_lookup("MAGIC_VAR", AWK_NUMBER, & value)
&& some_condition(value.num_value)) {
value.num_value += 42;
sym_update("MAGIC_VAR", & value);
}
return make_number(0.0, result);
}
上述代码在每次调用
magic()
函数时都会在符号表中查找
MAGIC_VAR
变量,效率较低。使用标量 cookie 可以优化此过程:
static awk_scalar_t magic_var_cookie; /* cookie for MAGIC_VAR */
static void
my_extension_init()
{
awk_value_t value;
/* install initial value */
sym_update("MAGIC_VAR", make_number(42.0, & value));
/* get the cookie */
sym_lookup("MAGIC_VAR", AWK_SCALAR, & value);
/* save the cookie */
magic_var_cookie = value.scalar_cookie;
…
}
/* do_magic --- do something really great */
static awk_value_t *
do_magic(int nargs, awk_value_t *result)
{
awk_value_t value;
if ( sym_lookup_scalar(magic_var_cookie, AWK_NUMBER, & value)
&& some_condition(value.num_value)) {
value.num_value += 42;
sym_update_scalar(magic_var_cookie, & value);
}
…
return make_number(0.0, result);
}
6. 创建和使用缓存值
可以使用以下函数创建和释放缓存值:
-
awk_bool_t create_value(awk_value_t *value, awk_value_cookie_t *result);
:从
value
创建一个缓存的字符串或数值,以便后续高效分配。只允许
AWK_NUMBER
和
AWK_STRING
类型的值,其他类型将被拒绝。
-
awk_bool_t release_value(awk_value_cookie_t vc);
:释放从
create_value()
获得的值 cookie 关联的内存。
使用值 cookie 可以节省存储空间,因为多个变量可以共享相同的值。以下是使用示例:
static awk_value_cookie_t answer_cookie; /* static value cookie */
static void
my_extension_init()
{
awk_value_t value;
char *long_string;
size_t long_string_len;
/* code from earlier */
…
/* … fill in long_string and long_string_len … */
make_malloced_string(long_string, long_string_len, & value);
create_value(& value, & answer_cookie); /* create cookie */
…
}
static awk_value_t *
do_magic(int nargs, awk_value_t *result)
{
awk_value_t new_value;
… /* as earlier */
value.val_type = AWK_VALUE_COOKIE;
value.value_cookie = answer_cookie;
sym_update("VAR1", & value);
sym_update("VAR2", & value);
…
sym_update("VAR100", & value);
…
}
gawk 内部使用引用计数的字符串,因此多个变量共享相同的字符串值不会有问题。当变量的值更改时,gawk 会减少旧值的引用计数,并更新变量以使用新值。最后,在清理操作中,应使用
release_value()
释放创建的任何缓存值。
7. 数组操作
awk 中的主要数据结构是关联数组,扩展需要能够操作 awk 数组。API 提供了一些数据结构和函数来处理数组。
7.1 数组数据类型
-
typedef void *awk_array_t;:如果请求数组变量的值,将返回一个awk_array_t值。该值对扩展是不透明的,它唯一标识数组,但只能通过将其传递给 API 函数或从 API 函数接收来使用,类似于<stdio.h>库例程中使用FILE *值的方式。 -
typedef struct awk_element { ... } awk_element_t;:这是一个 “扁平化” 的数组元素。awk在awk_flat_array_t内部生成这些元素的数组。可以标记单个元素以进行删除。新元素必须使用单独的 API 逐个添加。 -
typedef struct awk_flat_array { ... } awk_flat_array_t;:这是一个扁平化的数组。当扩展从 gawk 获取此结构时,elements数组的实际大小为count。opaque1和opaque2指针供 gawk 使用,因此扩展不能修改它们。
7.2 数组函数
-
与单个数组元素相关的函数
-
awk_bool_t get_element_count(awk_array_t a_cookie, size_t *count);:获取数组中的元素数量。如果发生错误,返回false。 -
awk_bool_t get_array_element(awk_array_t a_cookie, const awk_value_t *const index, awk_valtype_t wanted, awk_value_t *result);:返回数组中指定索引的元素值。如果wanted与实际类型不匹配或索引不在数组中,返回false。 -
awk_bool_t set_array_element(awk_array_t a_cookie, const awk_value_t *const index, const awk_value_t *const value);:在数组中创建或修改指定索引的元素。ARGV和ENVIRON数组不能更改,但PROCINFO数组可以。 -
awk_bool_t set_array_element_by_elem(awk_array_t a_cookie, awk_element_t element);:类似于set_array_element(),但从element中获取索引和值。 -
awk_bool_t del_array_element(awk_array_t a_cookie, const awk_value_t* const index);:从数组中删除指定索引的元素。如果元素被删除,返回true;否则返回false。
-
-
与整个数组相关的函数
-
awk_array_t create_array(void);:创建一个新数组。 -
awk_bool_t clear_array(awk_array_t a_cookie);:清除数组中的所有元素。如果发生问题,返回false;否则返回true。 -
awk_bool_t flatten_array(awk_array_t a_cookie, awk_flat_array_t **data);:创建并填充一个awk_flat_array_t结构,该结构表示数组的扁平化版本。如果成功,返回true;否则返回false。 -
awk_bool_t release_flattened_array(awk_array_t a_cookie, awk_flat_array_t *data);:释放扁平化数组的存储空间。如果成功,返回true;否则返回false。
-
以下是一个处理数组所有元素的示例:
@load "testext"
BEGIN {
n = split("blacky rusty sophie raincloud lucky", pets)
printf("pets has %d elements\n", length(pets))
ret = dump_array_and_delete("pets", "3")
printf("dump_array_and_delete(pets) returned %d\n", ret)
if ("3" in pets)
printf("dump_array_and_delete() did NOT remove index \"3\"!\n")
else
printf("dump_array_and_delete() did remove index \"3\"!\n")
print ""
}
对应的 C 代码实现如下:
static awk_value_t *
dump_array_and_delete(int nargs, awk_value_t *result)
{
awk_value_t value, value2, value3;
awk_flat_array_t *flat_array;
size_t count;
char *name;
int i;
assert(result != NULL);
make_number(0.0, result);
if (nargs != 2) {
printf("dump_array_and_delete: nargs not right "
"(%d should be 2)\n", nargs);
goto out;
}
/* get argument named array as flat array and print it */
if (get_argument(0, AWK_STRING, & value)) {
name = value.str_value.str;
if (sym_lookup(name, AWK_ARRAY, & value2))
printf("dump_array_and_delete: sym_lookup of %s passed\n",
name);
else {
printf("dump_array_and_delete: sym_lookup of %s failed\n",
name);
goto out;
}
} else {
printf("dump_array_and_delete: get_argument(0) failed\n");
goto out;
}
if (! get_element_count(value2.array_cookie, & count)) {
printf("dump_array_and_delete: get_element_count failed\n");
goto out;
}
printf("dump_array_and_delete: incoming size is %lu\n",
(unsigned long) count);
if (! flatten_array(value2.array_cookie, & flat_array)) {
printf("dump_array_and_delete: could not flatten array\n");
goto out;
}
if (flat_array->count != count) {
printf("dump_array_and_delete: flat_array->count (%lu)"
" != count (%lu)\n",
(unsigned long) flat_array->count,
(unsigned long) count);
goto out;
}
if (! get_argument(1, AWK_STRING, & value3)) {
printf("dump_array_and_delete: get_argument(1) failed\n");
goto out;
}
for (i = 0; i < flat_array->count; i++) {
printf("\t%s[\"%.*s\"] = %s\n",
name,
(int) flat_array->elements[i].index.str_value.len,
flat_array->elements[i].index.str_value.str,
valrep2str(& flat_array->elements[i].value));
if (strcmp(value3.str_value.str,
flat_array->elements[i].index.str_value.str) == 0) {
flat_array->elements[i].flags |= AWK_ELEMENT_DELETE;
printf("dump_array_and_delete: marking element \"%s\" "
"for deletion\n",
flat_array->elements[i].index.str_value.str);
}
}
if (! release_flattened_array(value2.array_cookie, flat_array)) {
printf("dump_array_and_delete: could not release flattened array\n");
goto out;
}
make_number(1.0, result);
out:
return result;
}
运行该测试的输出如下:
pets has 5 elements
dump_array_and_delete: sym_lookup of pets passed
dump_array_and_delete: incoming size is 5
pets["1"] = "blacky"
pets["2"] = "rusty"
pets["3"] = "sophie"
dump_array_and_delete: marking element "3" for deletion
pets["4"] = "raincloud"
pets["5"] = "lucky"
dump_array_and_delete(pets) returned 1
dump_array_and_delete() did remove index "3"!
7.3 创建和填充数组
除了处理 awk 代码创建的数组外,还可以创建并填充自己的数组,然后让 awk 代码访问和操作它们。创建数组时需要注意以下两点:
- 必须在创建新数组后立即将其安装到 gawk 的符号表中,然后再填充数组。如果将新数组作为现有数组的子数组安装,必须在添加任何元素之前将其添加到父数组中。
- 使用
sym_update()
将数组安装到 gawk 后,必须从传递给
sym_update()
的值中检索数组 cookie,然后再对其进行其他操作。
以下是一个创建包含两个常规元素和一个子数组的示例:
/* create_new_array --- create a named array */
static void
create_new_array()
{
awk_array_t a_cookie;
awk_array_t subarray;
awk_value_t index, value;
a_cookie = create_array();
value.val_type = AWK_ARRAY;
value.array_cookie = a_cookie;
if (! sym_update("new_array", & value))
printf("create_new_array: sym_update(\"new_array\") failed!\n");
a_cookie = value.array_cookie;
(void) make_const_string("hello", 5, & index);
(void) make_const_string("world", 5, & value);
if (! set_array_element(a_cookie, & index, & value)) {
printf("fill_in_array: set_array_element failed\n");
return;
}
(void) make_const_string("answer", 6, & index);
(void) make_number(42.0, & value);
if (! set_array_element(a_cookie, & index, & value)) {
printf("fill_in_array: set_array_element failed\n");
return;
}
(void) make_const_string("subarray", 8, & index);
subarray = create_array();
value.val_type = AWK_ARRAY;
value.array_cookie = subarray;
if (! set_array_element(a_cookie, & index, & value)) {
printf("fill_in_array: set_array_element failed\n");
return;
}
subarray = value.array_cookie;
(void) make_const_string("foo", 3, & index);
(void) make_const_string("bar", 3, & value);
if (! set_array_element(subarray, & index, & value)) {
printf("fill_in_array: set_array_element failed\n");
return;
}
}
以下是一个加载扩展并转储数组的示例脚本:
@load "subarray"
function dumparray(name, array, i)
{
for (i in array)
if (isarray(array[i]))
dumparray(name "[\"" i "\"]", array[i])
else
printf("%s[\"%s\"] = %s\n", name, i, array[i])
}
BEGIN {
dumparray("new_array", new_array);
}
运行该脚本的结果如下:
$ AWKLIBPATH=$PWD ./gawk -f subarray.awk
new_array["subarray"]["foo"] = bar
new_array["hello"] = world
new_array["answer"] = 42
通过以上内容,我们详细介绍了 gawk 扩展编程中 API 的各种功能,包括打印信息、更新
ERRNO
、请求值、访问和更新参数、符号表访问、创建和使用缓存值以及数组操作等。这些功能为开发者提供了强大的工具,能够更灵活地扩展 gawk 的功能。在实际应用中,开发者可以根据具体需求选择合适的函数和方法,提高程序的效率和性能。
gawk 扩展编程:API 功能详解与应用实践
8. 操作流程总结
在实际开发 gawk 扩展时,我们可以将上述各项功能的操作流程进行梳理,以便更清晰地理解和应用。以下是一个简单的 mermaid 流程图,展示了从初始化到数组操作和清理的主要步骤:
graph TD;
A[初始化扩展] --> B[设置初始值和获取 cookie];
B --> C[打印信息或更新 ERRNO];
C --> D[请求值和访问参数];
D --> E[符号表操作];
E --> F[创建和使用缓存值];
F --> G[数组操作];
G --> H[清理缓存和数组];
9. 不同操作的性能对比
在前面提到了使用标量 cookie 和缓存值可以提高性能,下面我们通过表格来对比一下不同操作的性能特点。
| 操作类型 | 性能特点 | 适用场景 |
| — | — | — |
| 直接符号表查找 | 每次访问都需要在符号表中查找,开销大,性能低 | 变量访问频率低的场景 |
| 使用标量 cookie | 避免每次符号表查找,提高访问和更新效率 | 变量频繁访问和更新的场景 |
| 不使用缓存值 | 每个变量的字符串值都需要单独分配内存,占用空间大 | 变量值差异大且内存充足的场景 |
| 使用缓存值 | 多个变量共享同一值,节省存储空间 | 多个变量有相同值的场景 |
10. 常见错误处理
在使用 gawk 扩展 API 时,可能会遇到各种错误,以下是一些常见错误及处理建议:
-
符号表查找失败
:如果
sym_lookup()
返回
false
,可能是变量名拼写错误、变量未定义或在特定上下文中不可用。需要检查变量名的正确性,并确保变量在使用前已经正确初始化。
-
数组操作错误
:在使用
get_array_element()
、
set_array_element()
等数组操作函数时,如果返回
false
,可能是索引不存在、数组类型不匹配或数组被锁定(如
ARGV
和
ENVIRON
数组)。需要检查索引的有效性和数组的权限。
-
缓存值创建失败
:
create_value()
可能会因为传入的值类型不是
AWK_NUMBER
或
AWK_STRING
而失败。在调用该函数前,要确保传入的值类型符合要求。
11. 代码优化建议
为了提高 gawk 扩展的性能和稳定性,我们可以遵循以下代码优化建议:
-
合理使用 cookie
:对于频繁访问和更新的变量,使用标量 cookie 来避免重复的符号表查找。在扩展初始化时获取 cookie,并在后续操作中使用。
-
缓存常用值
:如果多个变量需要使用相同的值,使用缓存值来节省存储空间。在扩展初始化时创建缓存值,并在需要时引用。
-
错误检查
:在调用每个 API 函数后,检查其返回值,确保操作成功。对于可能失败的操作,添加适当的错误处理代码,避免程序崩溃。
12. 综合示例
以下是一个综合示例,展示了如何在一个扩展中使用上述所有功能:
#include <stdio.h>
#include <assert.h>
#include "gawkapi.h"
static awk_scalar_t magic_var_cookie;
static awk_value_cookie_t answer_cookie;
static void my_extension_init() {
awk_value_t value;
// 安装初始值并获取 MAGIC_VAR 的 cookie
sym_update("MAGIC_VAR", make_number(42.0, &value));
sym_lookup("MAGIC_VAR", AWK_SCALAR, &value);
magic_var_cookie = value.scalar_cookie;
// 创建缓存值
char *long_string = "Hello, gawk!";
size_t long_string_len = strlen(long_string);
make_malloced_string(long_string, long_string_len, &value);
create_value(&value, &answer_cookie);
}
static awk_value_t *do_magic(int nargs, awk_value_t *result) {
awk_value_t value;
// 使用标量 cookie 获取和更新 MAGIC_VAR
if (sym_lookup_scalar(magic_var_cookie, AWK_NUMBER, &value) && value.num_value > 0) {
value.num_value += 10;
sym_update_scalar(magic_var_cookie, &value);
}
// 使用缓存值更新变量
value.val_type = AWK_VALUE_COOKIE;
value.value_cookie = answer_cookie;
sym_update("VAR1", &value);
sym_update("VAR2", &value);
// 打印信息
warning(EXT_ID, "Magic operation completed!");
return make_number(0.0, result);
}
static awk_value_t *dump_array_and_delete(int nargs, awk_value_t *result) {
awk_value_t value, value2, value3;
awk_flat_array_t *flat_array;
size_t count;
char *name;
int i;
assert(result != NULL);
make_number(0.0, result);
if (nargs != 2) {
fatal(EXT_ID, "dump_array_and_delete: nargs not right (%d should be 2)\n", nargs);
return result;
}
// 获取数组名和数组
if (get_argument(0, AWK_STRING, &value)) {
name = value.str_value.str;
if (sym_lookup(name, AWK_ARRAY, &value2)) {
printf("dump_array_and_delete: sym_lookup of %s passed\n", name);
} else {
fatal(EXT_ID, "dump_array_and_delete: sym_lookup of %s failed\n", name);
return result;
}
} else {
fatal(EXT_ID, "dump_array_and_delete: get_argument(0) failed\n");
return result;
}
// 获取数组元素数量
if (!get_element_count(value2.array_cookie, &count)) {
fatal(EXT_ID, "dump_array_and_delete: get_element_count failed\n");
return result;
}
printf("dump_array_and_delete: incoming size is %lu\n", (unsigned long)count);
// 扁平化数组
if (!flatten_array(value2.array_cookie, &flat_array)) {
fatal(EXT_ID, "dump_array_and_delete: could not flatten array\n");
return result;
}
if (flat_array->count != count) {
fatal(EXT_ID, "dump_array_and_delete: flat_array->count (%lu) != count (%lu)\n",
(unsigned long)flat_array->count, (unsigned long)count);
return result;
}
// 获取要删除的元素索引
if (!get_argument(1, AWK_STRING, &value3)) {
fatal(EXT_ID, "dump_array_and_delete: get_argument(1) failed\n");
return result;
}
// 遍历数组并标记要删除的元素
for (i = 0; i < flat_array->count; i++) {
printf("\t%s[\"%.*s\"] = %s\n",
name,
(int)flat_array->elements[i].index.str_value.len,
flat_array->elements[i].index.str_value.str,
valrep2str(&flat_array->elements[i].value));
if (strcmp(value3.str_value.str, flat_array->elements[i].index.str_value.str) == 0) {
flat_array->elements[i].flags |= AWK_ELEMENT_DELETE;
printf("dump_array_and_delete: marking element \"%s\" for deletion\n",
flat_array->elements[i].index.str_value.str);
}
}
// 释放扁平化数组
if (!release_flattened_array(value2.array_cookie, flat_array)) {
fatal(EXT_ID, "dump_array_and_delete: could not release flattened array\n");
return result;
}
make_number(1.0, result);
return result;
}
static void my_extension_cleanup() {
// 释放缓存值
release_value(answer_cookie);
}
在上述示例中,我们在
my_extension_init()
函数中进行了初始化操作,包括设置初始值、获取标量 cookie 和创建缓存值。在
do_magic()
函数中,我们使用标量 cookie 来高效地访问和更新变量,并使用缓存值来更新多个变量。同时,我们还调用了
warning()
函数来打印警告信息。在
dump_array_and_delete()
函数中,我们展示了如何进行数组操作,包括获取数组、扁平化数组、标记要删除的元素和释放扁平化数组。最后,在
my_extension_cleanup()
函数中,我们释放了创建的缓存值。
通过这个综合示例,我们可以看到如何将 gawk 扩展 API 的各个功能组合在一起,实现一个完整的扩展功能。在实际开发中,开发者可以根据具体需求对代码进行调整和扩展。
总之,gawk 扩展编程为开发者提供了丰富的功能和强大的工具,通过合理使用 API 函数和数据结构,我们可以实现高效、稳定的扩展,满足各种复杂的应用场景需求。在开发过程中,要注意性能优化、错误处理和代码规范,以确保扩展的质量和可靠性。
超级会员免费看
2

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



