Wednesday, May 13, 2009

一個awk+sed+bash的應用實例

motivation:在做一個project的時候,bench格式裏面的門描述字符串不被某個要用到的工具所辨認(主要是下劃綫等特殊字符)
object:把一個bench文件裏面的字符串替換成新的合適的字符串,如N1,N2...等
tools:awk,sed,bash

一個典型的bench例子如下:
# comment

INPUT(INPUT_NODE)
OUTPUT(OUTPUT_NODE)

SOME_NODE = ADD( INPUT_NODE, OUTPUT_NODE )

因此替換規則是,把INPUT_NODE換為N1,OUTPUT_NODE-> N2,SOME_NODE->N3,
並且它們所有隨後的出現也要替換。

思路:
  1. 使用awk做一個parser,利用其关联数组的功能存储新出现的node name,并分配一個新名字。當parse結束后,就得到了一份字典(map)
  2. 利用字典構造替換正則運算式,用sed進行全局替換。
  3. 編寫shell script把所有工作整合起來(glue)
主要工作是awk script:

BEGIN{
        counter=0;
        gate_names_str = "DFF|OR|ADD|NAND|NOR|NOT|AND|BUF"
        split(gate_names_str,gate_names,"|")
        split_regex = "[ ,)(=]"# gate_names
        #for( i in gate_names ) print gate_names[i];
}

function add2dict(node_name){
        if( dict[node_name] == "" ){
                dict[node_name]=sprintf("N%d",counter);
                counter++;
                #print node_name " " dict[node_name]
                # beautiful output
                printf("%-20s%s\n",node_name,dict[node_name]);
        }
}


/#/{
        # do nothing
}

/INPUT/ {
        # store each gate name into dict from INPUT(...)
        len = index($1,")")-7;
        str = substr($0,7,len);
        add2dict(str);
}

/OUTPUT/ {
        # store each gate name into dict from OUTPUT(...)
        len = index($1,")")-8;
        str = substr($0,8,len);
        add2dict(str);
}

/=/{
        # split using `='
        split($0,names,split_regex)
        for(i in names){
                if(names[i]=="") continue;
                # do not handle gate names
                ignore=0;
                for(j in gate_names){
                        if( names[i] == gate_names[j] ){
                                ignore=1;
                                break;
                        }
                }
                if(ignore==1) continue;
                add2dict(names[i]);
        }
}

END{
        #for(i in dict) print i " " dict[i]
}

主要的流程是,對INPUT,OUPUT, xxx = op(yyy,zzz,...) 三種不同的pattern做解析,
并extract出裏面的單詞,存入字典,注意要去除重複,並且不能把op存入字典。
awk的關聯数组真的很好用,并且for i in array這種for循环也非常方便。
split函数可以方便地把一个字符串根据split field分割并保存到数组里,并且还支持正则表达式的split field

然后是 生成 sed 的string, 这里是脚本内容:

#!/bin/bash

if [ $# -lt 1 ];then
        echo "Usage: ./replace_name.sh bench_name"
        exit 1
fi

# generate dictionary
awk -f gen_dict.awk "$1" > tmp
sort tmp > dict
# construct sed expression
echo -n "" > exp.sed
while read line
do
        from=${line%% *}
        to=${line##* }
        echo "s/${from}/${to}/g ">> exp.sed
done < "dict"

sed -f exp.sed "$1"

# remove extra file
rm -rf dict exp.sed tmp

其中 while read line ... done < "dict" 那段實現了從文件里逐行讀取。
而bash 的 字符串替換功能也很方便地把每行的兩個field存到不同变量里。
注意这里如果有多个field的话,也许用cut或awk来实现比较方便。

最后,针对每行生成一个替换的正则表达式,并提供给sed进行替换。

#END

No comments: