巧用bash的eval命令解析配置文件

文章讲述了在处理FPGA仿真平台HAPS的硬件模块寄存器导出脚本时遇到的性能问题。通过分析发现,大量使用awk进行key=value解析是瓶颈。作者提出使用eval命令配合cut来减少解析次数和提高效率,经过优化,脚本执行时间缩短了90%。这种方法尤其适用于key=value格式的配置文件解析。
摘要由CSDN通过智能技术生成

背景

最近工作上经常需要导出硬件模块的寄存器,而导出脚本在HAPS(一种FPGA仿真平台)的执行速度很慢,一开始我以为是执行导出的定制版lookat命令有问题,加了每个步骤的时间戳后没发现问题,于是转而寻找整个脚本的性能瓶颈。

介绍下脚本工作流程,大致就是逐行读取每个硬件模块的寄存器dump信息(每个模块占一行),再将dump信息解析出来,构造好寄存器基地址、size和输出路径名后,传递给lookat命令执行。

dump信息在文件中的编码如下:

[DUMP INFO]:pipe=0:token=ioblk_itp_rdma_0 dump_frm_buf=0 ptn=1 phyaddr=0x91b67ec0 size=3520

可以看出,基本是key=value这样的格式,导出脚本在解析上面这行配置信息时,大量使用了awk脚本:

    line_str=$1
    pipe_info=`echo ${line_str} | awk -F':' '{print $2}'`
    pipe_id=`echo ${pipe_info} | awk -F'=' '{print $2}'`

    # parse info
    cmdbuf_mem_info=`echo ${line_str} | awk -F':' '{print $3}'`
    token=`echo ${cmdbuf_mem_info} | awk '{print $1}' | awk -F'=' '{print $2}'`
    buf_idx=`echo ${cmdbuf_mem_info} | awk '{print $2}' | awk -F'=' '{print $2}'`
    ptn_idx=`echo ${cmdbuf_mem_info} | awk '{print $3}' | awk -F'=' '{print $2}'`
    phyaddr=`echo ${cmdbuf_mem_info} | awk '{print $4}' | awk -F'=' '{print $2}'`
    size=`echo ${cmdbuf_mem_info} | awk '{print $5}' | awk -F'=' '{print $2}'`

	# 调用lookat
	lookat ${phyaddr} -t ${size} -f "${out_dir}/pipe_${pipe_id}/buf_${buf_idx}/bin/ptn_${ptn_idx}/${token}.bin"

可以看出,脚本解析的思路就是先用冒号:或空格 分离出想要的key=value对,再用等号=分离出value,而key则是临时定义的环境变量。

在解析代码的前后加时间戳,发现这坨东西就是性能瓶颈!

优化思路

关键是减少awk的使用

虽然awk跟HAPS上的其他常用Linux命令都是链接到busybox,但我猜测不同的命令对应的是不同的busybox代码段,所以cut的运行开销大概率是比awk低的。

不过用cut替换awk的性能提升应该有限,最关键的是减少解析key=value的次数。

解决方案

eval命令解析key=value

注意到dump信息的格式都是key=value,等号前后没有空格,这不恰好是环境变量的定义格式吗?我可以把这些空格隔开的key=value传递给eval命令,eval就会在当前bash里创建跟key同名的环境变量,并且其值就是value,正好实现了内层解析!

eval命令的手册:

       eval [arg ...]
              The  args  are  read and concatenated together into a single
              command.  This command is then  read  and  executed  by  the
              shell, and its exit status is returned as the value of eval.
              If there are no args, or only null arguments,  eval  returns
              0.

示例:

eval "ptn=1 phyaddr=0x91b67ec0 size=3520"
echo $ptn $phyaddr $size

示例输出:

1 0x91b67ec0 3520

cut命令分离出key=value

cut命令可以根据用户指定的分隔符分割字符串,考虑到dump信息的分隔符不统一,有些是冒号有些是空格,而我们需要统一成空格,所以需要分别指定cut的输入分隔符和输出分隔符:
cut命令的手册:

BusyBox v1.35.0 (2023-01-13 13:00:13 CST) multi-call binary.

Usage: cut [OPTIONS] [FILE]...

Print selected fields from FILEs to stdout

        -b LIST Output only bytes from LIST
        -c LIST Output only characters from LIST
        -d SEP  Field delimiter for input (default -f TAB, -F run of whitespace)
        -O SEP  Field delimeter for output (default = -d for -f, one space for -F)
        -D      Don't sort/collate sections or match -fF lines without delimeter
        -f LIST Print only these fields (-d is single char)
        -F LIST Print only these fields (-d is regex)
        -s      Output only lines containing delimiter
        -n      Ignored

示例:

echo "[DUMP INFO]:pipe=0:token=ioblk_itp_rdma_0 dump_frm_buf=0 ptn=1 phyaddr=0x91b67ec0 size=3520" | cut -d ':' -O ' ' -F 2,3 

示例输出:

pipe=0 token=ioblk_itp_rdma_0 dump_frm_buf=0 ptn=1 phyaddr=0x91b67ec0 size=3520

用反引号将cut的输出转换成eval的输入

因为标准输出和命令行输入是不同的IO途径,因此需要用反引号实现转换,即Command Substitution


   Command Substitution
       Command substitution allows the output of a command to replace the command name.  There are two forms:

              $(command)
       or
              `command`

综合起来

那一坨解析代码优化为下面一行:

eval `echo ${line_str} | cut -d ':' -O ' ' -F 2,3`

# 调用lookat
lookat ${phyaddr} -t ${size} -f "${out_dir}/pipe_${pipe}/buf_${dump_frm_buf}/bin/ptn_${ptn}/${token}.bin"

可以看出,最终输出跟那一坨完全一致,只是环境变量pipe_id重命名成dump信息自带的pipe,buf_idx重命名成自带的dump_frm_buf,ptn_idx重命名成自带的ptn,完美!

优化效果

脚本执行时间缩短了90%

总结

  • eval命令特别适合key=value这种格式的配置文件的解析。
  • profiling很重要,不然很容易误导优化方向。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值