如何用Bash/Awk高效优化Ksh脚本生成程序调用树CSV
程序调用树形视图生成的高效实现方案
问题背景
需要从两个文件生成CSV格式的程序调用树形视图:
- 正在使用的程序列表:共2926行,包含需要生成树的根程序
- 程序关联列表:共23922行,格式示例如下:
A|B C D E B|F G C|J K L E| K|Z L|M N Z|Y X
每行表示父程序调用的子程序集合,例如A|B C D E表示A调用B、C、D、E。
预期输出格式(以A为例):
A ;B ;;F ;;G ;C ;;J ;;K ;;;Z ;;;;Y ;;;;X ;;L ;;;M ;;;N ;E
现有Ksh脚本耗时3-4小时,急需优化为更高效的纯Awk实现。
现有脚本的性能瓶颈
- 频繁启动子进程:主脚本循环调用
csv_tree.sh,每次启动新Shell进程,累计开销巨大 - 重复扫描文件:递归过程中反复用
grep/awk扫描2万多行的关联文件,IO和CPU消耗极高 - 低效字符串操作:循环生成
;分隔符、多次字符串拼接,进一步拖慢速度
纯Awk高效实现方案
核心思路
- 一次性将程序关联列表加载到内存数组,避免重复IO
- 用Awk递归函数遍历调用树,全程在单进程内完成
- 预计算分隔符并缓存,减少字符串拼接开销
完整Awk脚本
#!/usr/bin/awk -f # 第一步:加载程序关联列表到内存数组 BEGIN { FS = "|" # 读取关联文件(第一个参数) while ((getline < ARGV[1]) > 0) { parent = $1 # 分割子程序列表,过滤空值后存入数组 split($2, kids, /[[:space:]]+/) delete temp temp_len = 0 for (k in kids) { if (kids[k] != "") { temp[++temp_len] = kids[k] } } # 将子程序列表用空格拼接存储 children[parent] = temp_len > 0 ? temp[1] : "" for (i=2; i<=temp_len; i++) { children[parent] = children[parent] " " temp[i] } } close(ARGV[1]) ARGV[1] = "" # 标记第一个参数已处理 # 缓存分隔符前缀,避免重复生成 sep_cache[0] = "" } # 第二步:处理程序列表中的每个根程序 { root = $1 # 输出根节点到对应文件 print root > root "_tree.csv" # 递归遍历子节点,初始层级为1 traverse(root, 1) close(root "_tree.csv") } # 递归遍历函数:parent=父程序,level=当前层级 function traverse(parent, level, sep, kids, kid, i) { # 缓存当前层级的分隔符 if (!(level in sep_cache)) { sep_cache[level] = sep_cache[level-1] ";" } sep = sep_cache[level] # 获取父程序的子程序列表,无子程序则直接返回 kids_str = children[parent] if (kids_str == "") return split(kids_str, kids, /[[:space:]]+/) for (i in kids) { kid = kids[i] # 输出当前节点 print sep kid > parent "_tree.csv" # 递归遍历子节点,层级+1 traverse(kid, level+1) } }
使用方法
- 将上述脚本保存为
generate_tree.awk,赋予执行权限:chmod +x generate_tree.awk - 运行命令:
./generate_tree.awk 程序关联列表文件 正在使用的程序列表文件
性能优势
- 仅扫描关联文件一次,全程内存操作,IO开销极低
- 单进程执行,无频繁子进程启动开销
- 分隔符缓存复用,避免重复生成字符串
- 递归遍历逻辑简洁高效,处理2926个根程序+23922条关联数据,耗时可压缩到数秒级别
内容的提问来源于stack exchange,提问作者yo Ni
相关产品推荐
相关产品推荐

