41、gawk扩展功能全解析

gawk扩展功能全解析

1. 代码加载与初始化

在编写gawk扩展时,需要完成一系列的代码加载与初始化工作。首先是更新错误号并返回结果的代码:

update_ERRNO_int(errno);
return make_number(ret, result);

还有通过 fill_stat_array() 函数完成繁琐工作,完成后返回其结果:

ret = fill_stat_array(name, array, & sbuf);
return make_number(ret, result);

为了将新函数加载到gawk中,需要提供“粘合代码”。例如 filefuncs 扩展有一个初始化函数:

/* init_filefuncs --- initialization routine */
static awk_bool_t
init_filefuncs(void)
{
    …
}

还需要一个 awk_ext_func_t 结构的数组来加载每个函数:

static awk_ext_func_t func_table[] = {
    { "chdir", do_chdir, 1 },
    { "stat",  do_stat, 2 },
#ifndef __MINGW32__
    { "fts",   do_fts, 3 },
#endif
};

每个扩展必须有一个名为 dl_load() 的例程来加载所有需要加载的内容,可使用 gawkapi.h 中的 dl_load_func() 宏:

/* define the dl_load() function using the boilerplate macro */
dl_load_func(func_table, filefuncs, "")
2. 扩展的集成

编写好代码后,需要在运行时将其添加到正在运行的gawk解释器中。首先要编译代码,假设函数在名为 filefuncs.c 的文件中, idir gawkapi.h 头文件的位置,创建GNU/Linux共享库的步骤如下:

$ gcc -fPIC -shared -DHAVE_CONFIG_H -c -O -g -Iidir filefuncs.c
$ gcc -o filefuncs.so -shared filefuncs.o

库创建好后,使用 @load 关键字加载:

# file testff.awk
@load "filefuncs"
BEGIN {
    "pwd" | getline curdir  # save current directory
    close("pwd")
    chdir("/tmp")
    system("pwd")   # test it
    chdir(curdir)   # go back
    print "Info for testff.awk"
    ret = stat("testff.awk", data)
    print "ret =", ret
    for (i in data)
        printf "data[\"%s\"] = %s\n", i, data[i]
    print "testff.awk modified:",
        strftime("%m %d %Y %H:%M:%S", data["mtime"])
    print "\nInfo for JUNK"
    ret = stat("JUNK", data)
    print "ret =", ret
    for (i in data)
        printf "data[\"%s\"] = %s\n", i, data[i]
    print "JUNK modified:", strftime("%m %d %Y %H:%M:%S", data["mtime"])
}

设置 AWKLIBPATH 环境变量告诉gawk在哪里查找扩展,然后运行程序:

$ AWKLIBPATH=$PWD gawk -f testff.awk
3. 常见扩展功能介绍
3.1 文件相关函数

filefuncs 扩展提供了三个不同的函数:
- 加载扩展

@load "filefuncs"
  • chdir() 函数 :用于更改当前目录,是对 chdir() 系统调用的直接挂钩。成功返回零,出错返回小于零的值,并更新 ERRNO
result = chdir("/some/directory")
  • stat() 函数 :提供对 stat() 系统调用的挂钩。成功返回零,出错返回小于零的值,并更新 ERRNO 。默认使用 lstat() 系统调用,若传入第三个参数则使用 stat() 。调用成功时,会将从文件系统检索到的信息填充到 statdata 数组中。
result = stat("/some/path", statdata [, follow])

statdata 数组填充信息如下表所示:
| 下标 | struct stat 字段 | 文件类型 |
| ---- | ---- | ---- |
| “name” | 文件名 | 所有 |
| “dev” | st_dev | 所有 |
| “ino” | st_ino | 所有 |
| “mode” | st_mode | 所有 |
| “nlink” | st_nlink | 所有 |
| “uid” | st_uid | 所有 |
| “gid” | st_gid | 所有 |
| “size” | st_size | 所有 |
| “atime” | st_atime | 所有 |
| “mtime” | st_mtime | 所有 |
| “ctime” | st_ctime | 所有 |
| “rdev” | st_rdev | 设备文件 |
| “major” | st_major | 设备文件 |
| “minor” | st_minor | 设备文件 |
| “blksize” | st_blksize | 所有 |
| “pmode” | 模式值的可读版本,如 ls 打印的那样 | 所有 |
| “linkval” | 符号链接的值 | 符号链接 |
| “type” | 文件类型的字符串表示 | 所有 |

  • fts() 函数 :提供对C库 fts() 例程的挂钩,用于遍历文件层次结构。它会填充一个多维数组,包含所请求层次结构中遇到的每个文件和目录的数据。
flags = or(FTS_PHYSICAL, ...)
result = fts(pathlist, flags, filedata)

参数说明:
- pathlist :文件名数组,使用元素值,忽略索引值。
- flags :应是以下预定义常量标志值的按位或,至少提供 FTS_LOGICAL FTS_PHYSICAL 之一,否则 fts() 返回错误值并设置 ERRNO
- FTS_LOGICAL :进行“逻辑”文件遍历,符号链接返回的信息指的是链接到的文件。
- FTS_PHYSICAL :进行“物理”文件遍历,符号链接返回的信息指的是符号链接本身。
- FTS_NOCHDIR :禁用C库 fts() 例程在遍历文件层次结构时更改目录的优化。
- FTS_COMFOLLOW :立即跟随 pathlist 中命名的符号链接。
- FTS_SEEDOT :包含 .. 条目。
- FTS_XDEV :遍历期间不跨越不同的挂载文件系统。
- filedata :用于保存结果的数组。

fts() 函数根据路径是文件还是目录,填充 filedata 数组的方式不同,具体流程如下:

graph TD
    A[开始] --> B{路径是文件?}
    B -- 是 --> C[数组包含path、stat、error元素]
    B -- 否 --> D[数组为目录,每个条目对应一个元素]
    D --> E{条目是文件?}
    E -- 是 --> F[元素同文件情况]
    E -- 否 --> G[元素递归描述子目录]
    C --> H[结束]
    F --> H
    G --> H
3.2 fnmatch() 接口

该扩展提供对C库 fnmatch() 函数的接口:
- 加载扩展

@load "fnmatch"
  • 使用函数
result = fnmatch(pattern, string, flags)

返回值:成功为零,字符串不匹配模式为 FNM_NOMATCH ,出错为其他非零值。除了 fnmatch() 函数,该扩展还添加了一个常量 FNM_NOMATCH 和一个名为 FNM 的标志值数组。参数说明:
- pattern :要匹配的文件名通配符。
- string :文件名字符串。
- flag :零或 FNM 数组中一个或多个标志的按位或。
标志如下表所示:
| 数组元素 | fnmatch() 定义的对应标志 |
| ---- | ---- |
| FNM["CASEFOLD"] | FNM_CASEFOLD |
| FNM["FILE_NAME"] | FNM_FILE_NAME |
| FNM["LEADING_DIR"] | FNM_LEADING_DIR |
| FNM["NOESCAPE"] | FNM_NOESCAPE |
| FNM["PATHNAME"] | FNM_PATHNAME |
| FNM["PERIOD"] | FNM_PERIOD |

示例:

@load "fnmatch"
…
flags = or(FNM["PERIOD"], FNM["NOESCAPE"])
if (fnmatch("*.a", "foo.c", flags) == FNM_NOMATCH)
    print "no match"
3.3 fork() wait() waitpid() 接口

fork 扩展添加了三个函数:
- 加载扩展

@load "fork"
  • fork() 函数 :创建一个新进程,在子进程中返回零,在父进程中返回子进程的进程ID号,出错返回 -1,并通过 ERRNO 指示问题。
pid = fork()
  • waitpid() 函数 :等待指定进程ID的进程,返回 waitpid() 系统调用的值。
ret = waitpid(pid)
  • wait() 函数 :等待第一个子进程死亡,返回 wait() 系统调用的值。
ret = wait()

示例:

@load "fork"
…
if ((pid = fork()) == 0)
    print "hello from the child"
else
    print "hello from the parent"
3.4 启用就地文件编辑

inplace 扩展模拟GNU sed的 -i 选项,对每个输入文件进行“就地”编辑。使用 inplace.awk 包含文件正确调用扩展:

# inplace --- load and invoke the inplace extension.
@load "inplace"
# Please set INPLACE_SUFFIX to make a backup copy.  For example, you may
# want to set INPLACE_SUFFIX to .bak on the command line or in a BEGIN rule.
BEGINFILE {
    inplace_begin(FILENAME, INPLACE_SUFFIX)
}
ENDFILE {
    inplace_end(FILENAME, INPLACE_SUFFIX)
}

对于每个处理的常规文件,扩展将标准输出重定向到一个临时文件,处理完成后恢复标准输出。如果 INPLACE_SUFFIX 不为空字符串,会创建原文件的备份。

示例:

$ gawk -i inplace '{ gsub(/foo/, "bar") }; { print }' file1 file2 file3

保留原文件备份:

$ gawk -i inplace -v INPLACE_SUFFIX=.bak '{ gsub(/foo/, "bar") }
> { print }' file1 file2 file3
3.5 字符和数值转换: ord() chr()

ordchr 扩展添加了两个函数:
- 加载扩展

@load "ordchr"
  • ord() 函数 :返回字符串中第一个字符的数值。
number = ord(string)
  • chr() 函数 :返回一个字符串,其第一个字符由给定数值表示。
char = chr(number)

示例:

@load "ordchr"
…
printf("The numeric value of 'A' is %d\n", ord("A"))
printf("The string value of 65 is %s\n", chr(65))
3.6 读取目录

readdir 扩展为目录添加一个输入解析器:
- 加载扩展

@load "readdir"

使用该扩展时,命令行中指定的目录不会被跳过,而是被读取,每个条目作为一条记录返回。记录由三个字段组成,前两个是inode号和文件名,用斜杠分隔。在支持文件类型信息的系统上,记录有第三个字段,表示文件类型,对应关系如下表所示:
| 字母 | 文件类型 |
| ---- | ---- |
| b | 块设备 |
| c | 字符设备 |
| d | 目录 |
| f | 常规文件 |
| l | 符号链接 |
| p | 命名管道(FIFO) |
| s | 套接字 |
| u | 其他(未知) |

示例:

@load "readdir"
...
BEGIN { FS = "/" }
{ print "filename is", $2 }
3.7 反转输出

revoutput 扩展添加一个简单的输出包装器,反转每个输出行中的字符:
- 加载扩展

@load "revoutput"

示例:

BEGIN {
    REVOUT = 1
    print "don't panic" > "/dev/stdout"
}

输出为 cinap t'nod

3.8 双向I/O示例

revtwoway 扩展添加一个简单的双向处理器,反转发送给它的每行字符,供awk程序读取:
- 加载扩展

@load "revtwoway"

示例:

BEGIN {
    cmd = "/magic/mirror"
    print "don't panic" |& cmd
    cmd |& getline result
    print result
    close(cmd)
}

输出也为 cinap t'nod

3.9 数组的转储和恢复

rwarray 扩展添加两个函数:
- 加载扩展

@load "rwarray"
  • writea() 函数 :将数组转储到指定文件,成功返回1,失败返回0。
ret = writea(file, array)
  • reada() 函数 :从指定文件读取数据填充到数组,成功返回1,失败返回0。
ret = reada(file, array)

示例:

@load "rwarray"
…
ret = writea("arraydump.bin", array)
…
ret = reada("arraydump.bin", array)
3.10 读取整个文件

readfile 扩展添加一个函数和一个输入解析器:
- 加载扩展

@load "readfile"
  • readfile() 函数 :读取指定文件的全部内容,出错返回空字符串并设置 ERRNO
result = readfile("/some/path")

如果 PROCINFO["readfile"] 存在,扩展会激活一个输入解析器,将每个输入文件作为一个整体返回。

示例:

@load "readfile"
…
contents = readfile("/path/to/file");
if (contents == "" && ERRNO != "") {
    print("problem reading file", ERRNO) > "/dev/stderr"
    ...
}
3.11 扩展时间函数

time 扩展添加两个函数:
- 加载扩展

@load "time"
  • gettimeofday() 函数 :返回自1970 - 01 - 01 UTC以来经过的秒数,以浮点值表示。如果时间不可用,返回 -1 并设置 ERRNO
the_time = gettimeofday()
  • sleep() 函数 :尝试休眠指定的秒数,秒数可以是浮点数。如果秒数为负或休眠失败,返回 -1 并设置 ERRNO ,否则休眠结束后返回0。
result = sleep(seconds)
4. 扩展功能使用总结与注意事项

4.1 加载扩展的统一方式

在使用各种扩展功能时,大部分扩展都采用 @load 关键字来加载,例如:

@load "filefuncs"
@load "fnmatch"
@load "fork"
# 其他扩展以此类推

这种统一的加载方式使得在gawk程序中引入新功能变得简单直接。

4.2 错误处理

许多扩展函数在出错时会更新 ERRNO ,在编写程序时,应该对这些错误情况进行处理,以保证程序的健壮性。例如在使用 readfile() 函数时:

@load "readfile"
…
contents = readfile("/path/to/file");
if (contents == "" && ERRNO != "") {
    print("problem reading file", ERRNO) > "/dev/stderr"
    ...
}

这样可以及时发现并处理可能出现的错误。

4.3 数组操作注意事项

在使用涉及数组操作的扩展函数,如 stat() fts() 时,要注意数组的填充和使用。对于 stat() 函数填充的 statdata 数组,要清楚每个下标的含义;对于 fts() 函数填充的 filedata 数组,要根据路径是文件还是目录来正确访问其中的元素。

4.4 性能考虑

一些扩展函数可能会对性能产生影响,例如 fts() 函数在遍历大型文件层次结构时可能会消耗较多的时间和资源。在使用这些函数时,要根据实际情况进行评估和优化。

4.5 跨平台兼容性

不同的扩展函数在不同的平台上可能有不同的实现和行为。例如, fts() 函数在不同的操作系统上可能会有细微的差异,在编写跨平台的gawk程序时,需要考虑这些因素。

5. 扩展功能的应用场景示例

5.1 文件管理场景

  • 批量文件信息获取 :使用 filefuncs 扩展的 stat() 函数可以批量获取多个文件的详细信息,例如:
@load "filefuncs"
BEGIN {
    files["file1.txt"]
    files["file2.txt"]
    for (file in files) {
        ret = stat(file, data)
        if (ret == 0) {
            print "Info for", file
            for (i in data) {
                printf "data[\"%s\"] = %s\n", i, data[i]
            }
        }
    }
}
  • 文件目录遍历 filefuncs 扩展的 fts() 函数可以用于遍历文件目录,获取目录下所有文件和子目录的信息,例如查找某个目录下所有的文本文件:
@load "filefuncs"
BEGIN {
    pathlist["/some/directory"]
    flags = or(FTS_PHYSICAL)
    ret = fts(pathlist, flags, filedata)
    if (ret == 0) {
        for (path in filedata) {
            if (is_file_with_extension(filedata[path], ".txt")) {
                print "Found text file:", path
            }
        }
    }
}

function is_file_with_extension(file_info, ext) {
    if ("stat" in file_info) {
        return substr(file_info["path"], length(file_info["path"]) - length(ext) + 1) == ext
    }
    return 0
}

5.2 模式匹配场景

使用 fnmatch 扩展可以进行文件名的模式匹配,例如查找当前目录下所有以 .log 结尾的文件:

@load "fnmatch"
BEGIN {
    "ls" | getline files
    split(files, file_array)
    flags = or(FNM["FILE_NAME"])
    for (i in file_array) {
        if (fnmatch("*.log", file_array[i], flags) == 0) {
            print "Matched file:", file_array[i]
        }
    }
}

5.3 进程管理场景

fork 扩展可以用于创建新进程,实现并发操作。例如,同时处理多个任务:

@load "fork"
BEGIN {
    tasks["task1"]
    tasks["task2"]
    for (task in tasks) {
        pid = fork()
        if (pid == 0) {
            # 子进程执行的任务
            print "Processing task:", task
            exit
        }
    }
    # 父进程等待所有子进程结束
    while ((ret = wait()) != -1) {
        # 等待操作
    }
}

6. 总结

gawk的扩展功能为用户提供了丰富的工具,可以满足各种不同的需求。通过使用这些扩展,用户可以在gawk程序中实现文件管理、模式匹配、进程管理等功能。在使用扩展功能时,要注意加载方式、错误处理、数组操作、性能和跨平台兼容性等问题。同时,根据不同的应用场景,合理选择和使用扩展函数,可以提高程序的效率和功能。

以下是一个简单的流程图,展示了使用扩展功能的一般流程:

graph LR
    A[编写gawk程序] --> B[加载扩展]
    B --> C[调用扩展函数]
    C --> D{是否出错?}
    D -- 是 --> E[处理错误]
    D -- 否 --> F[使用函数结果]
    E --> F
    F --> G[程序结束]

通过以上对gawk扩展功能的介绍和分析,希望能帮助读者更好地理解和使用这些扩展,从而编写出更强大、更灵活的gawk程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值