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程序。
超级会员免费看
7

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



