AWK 实战总结2 -- 多行 record 以及 AWK 与系统的交互
[TOC]
本文介绍一种在开发高级编程语言时可以应用 AWK 语言作为工具的案例, 并顺便介绍 AWK 处理多行 record 时的用法, 浅析 AWK 与系统之间的交互通道.
场景介绍
在使用 C++ 编程时, 接口变化时, 我们可能需要一行行地去核对相关接口变量的变动, 不仅浪费时间, 也很容易眼睛看花犯错误. 如果能够通过数行代码来自动化替换, 方便并且容易排查问题. 但大多数情况下编辑器会帮我们解决一大部分内容, 例如补全功能. 或者从一开始设计接口时就不应该设计成松散的列举, 而是使用类等封装结构减少人眼校验的问题. 但是还是有一些 corner case. 我这里举出 2 个实际遇到的例子.
- ROS msg 的写入.
- 全局变量的初始化.
例如, 接口文件 source.h
内容如下, 其中接口变量 int i1
变为了 int i11
.
1 |
|
用到这些变量的地方有两处需要更新.
target.cpp
中需要在每隔一段时间对接口变量进行初始化:
1 |
|
我们需要把这些接口变量放入 ROSDebugMsg.msg
文件中, 方便 dump 成 rosbag 排查接口变量在运行过程中的变化.
1 | float64 d1 |
这里只用了 3 个变量, 如果这些变量有数十个, 几百个, 甚至更多呢? 仅仅靠人眼每次增删改是不现实的. 但是这种情况下, 编辑器没有什么办法帮我们. 因为要在运行期前完成功能, 也无法使用 C++ 程序自己去动态解析这些变化(基于模板元编程的反射机制). 再加上这种变量的出现大概率本来就是开发调试期间的临时做法(全局变量毕竟不是最终形态), 因此更灵活短小的脚本语言更方便使用. 这里采用 AWK 语言.
前提: 为了不让问题过于复杂化, 这里把所有的接口变量都设置为 double
类型. 对于更多类型可以考虑应用 AWK 的关联数组.
多行 record 的处理
AWK 的默认 RS
是 \n
, 即换行符. 也就是每个 record 就是一行. 但有时候我们需要将多行的文本当作 record 对象, 如何做呢?
可以参考 gwak 官网给出的建议.
这里整理如下:
修改
RS
例如在target.cpp
中预先埋下的锚点// ANC
作为分隔符.使用 ranger 匹配, 使用正则表达式匹配特定 2 行间的所有内容, 不需要预先埋点. 这种做法会把前后的符合正则表达式的两个语句也包含进来, 处理起来可能没那么规整.
1 | awk '/void init_extern()/,/void next_func()/ {print}' target.cpp |
- 对每行埋点, 这样只需要正常匹配, 把所有符合锚的行综合在一起变成多行. 这种思路在所需行不是整块分布而是零零散散分布时很有效. 但是需要较麻烦地添加看起来可能意义不明的注释. PS. 全局变量一般会在链接后初始化值为 0, 此处的初始化为 0 只是为了演示.
1 |
|
本文采取第一种方式.
代码示例
1 |
|
代码里没有注释, 这里稍微写一下.
mktemp
: 创建临时文件, 会更加安全.RS="'"$anchor_string"'"
: 将 bash 里的变量传给 AWK, 本来可以通过"'$var'"
的形式, 但是本例中字符串变量里有空格, 因此需要再套一层""
.$0="";
: 整块替换.print "'"${anchor_string}"'";
: 2 个此语句, 对替换后的块前后再次加入锚点, 方便下次处理.while((getline line < "./'${src_temp}'") > 0)
: 逐行读入.close("./'${src_temp}'")
: 关闭文件, 养成良好习惯.command = ("if ! grep -w " $1 " " "ROSDebugMsg.msg" " ;then echo " $1 " ; fi" )
与while((command | getline var) > 0)
: 此处应用了 AWK 与 Unix 其他命令交互的 getline 模式. 将source.h
中的每行变量名取出, 并且通过grep -w
精确匹配到ROSDebugMsg.msg
里, 将匹配的变量直接从grep
的输出中拿出来(已经是float64 var
的格式了)到var
中, 而不匹配的变量则会因为if
的 false 进入echo $1
的分支中将source.h
中的变量名打印出来. 这是故意用复杂的思路解决问题, 更简单的做法是删除ROSDebugMsg.msg
全部然后重新写入.
PS. 除了使用if-else
判断外, 对grep
匹配成功与否的输出还可以使用条件语句:grep -w $1 ROSDebugMsg.msg || echo $1
, 更加简洁.if(temp == var)
print "float64 "var;
: 由于之前已经将source.h
中的变量名都保存下来了, 如果没有匹配到,grep -w
的输出var
应该与temp
相同, 因此手动加上float64
前缀, 打印出来. 如果匹配成功var
与temp
不同, 一个是变量名, 例如d33
而另一个是float64 d3
.
延伸
除了使用上面的 command | getline
方式进行 AWK 与系统其他命令之间的交互以外, 还可以使用 gawk 下的 |&
operator 进行如下模式的操作:
1 | print "some query" |& "db_server" |
AWK 通过 print
将参数传递给系统命令 "db_server"
, 系统命令将输出结果再通过 getline
传递回 AWK. 我尝试使用 |&
进行实现同样的功能, 但是发现 grep
命令的表现一直报错, 说命令使用格式错误, 但是我用 wc
echo
等命令测试正常, 因为不是 AWK 标准里的, 后面有精力再研究一下 gawk 的这项功能的具体原理.
getline
作为 AWK 接收输入的窗口总共有如下使用模式:
最后一列标记为 gawk 的为 gawk 拓展的功能, 不一定符合 POSIX 标准.
system
与 getline
区别
最后稍微提及一下 system
getline
的区别.
system
也可以作为调用系统里其他命令的接口, 例子如下:
1 | END { |
与 getline
在此方面不同的是:
system
需要系统支持, 不是所有的系统都能运行system
.system
只会返回 command’s exit status, 而不是命令本身的输出结果. 因此只适合做开环的调用, “is useful for running large self-contained programs”.
更多信息可以参考 gawk 的 manual.
AWK 实战总结2 -- 多行 record 以及 AWK 与系统的交互