bash这么“古老”的东西从很久以前就支持这么“新潮”的概念, 我承认我一度惊呆了。

元编程?

如果你知道这是神马,可以跳过这节内容;如果你不知道这 是神马,看完下面这句话之后也可以跳过这节内容: 元编程就是把程序作为数据处理的过程。

OK这节内容就到此为止。

问题

今天在公司打酱油的时候被逮住了,某人说他想要这么个功 能:替换指定文本文件里的若干个指定模式——像把abc和def 分别替换成uvw和xyz——并且在我有机会想“妹的不会自己写 啊”之前就大方承认他妹的真的不会写。

哥,不会写没关系,但是你确定我帮你写出来之后你能看明 白吗……

解决方案

好吧用bash脚本写字符串算法不是不可以,但是这种行为学 名叫“作死”,我们并不提倡;直接使用各种文本处理命令会 比较上道。

说到“替换”字符串,第一个反应是sed;要替换很多个字 符串,第一个反应就是很多个sed……至于怎么搞出很多个 sed来呢,鉴于本人是专业写Erlang的,用循环什么的就 弱爆了——这里用的是管道,具体可以拆解成两个步骤:

  1. 对每个要被替换的模式,生成执行替换的bash函数
  2. 用管道将所有替换函数连接起来

例如,我们定义了函数subst_func_1,它将标准输入流 里的abc替换成uvw,另一个函数subst_func_2则是将def 替换成xyz;要“同时”替换abc和def,只要将这两个函数用 管道连接到一起就可以了:

1
cat source.txt | subst_func_1 | subst_func_2 > dest.txt

问题是,subst_func_1这样的函数要怎么定义?写死在 脚本里?那难道有多少个模式要替换就要写多少个函数? 当然不可能。鉴于替换函数的结构基本上是一样的,我们可 以在eval后面写一个这样的模板:

1
2
3
4
eval "
    function $name () {
        sed \"s/$from/$to/g\"
    }"

然后只要提供namefromto这三个变量就万事俱 备了。没错这个模板的替换和解析过程就是前面算是提到 过的“把程序作为数据处理的过程”——元编程

说实话我是写完这段代码才意识到它能被看作元编程的, bash这么“古老”的东西从很久以前就支持这么“新潮”的概 念,我承认我一度惊呆了。

语言的元编程特性

  1. 其实所有编程语言都能做元编程,只不过内置元编程 支持的语言一般不需要显式调用自己的编译器(解释 器),而是提供了其他更方便、更安全的元编程接口;
  2. 所有类似eval的操作其实都是元编程接口;
  3. 元编程的核心步骤是将数据转换为程序执行。

完整程序

关于bash的子进程

写这个东西的时候遇到一个“奇怪”的问题,以前重来没 有留意过;如果将程序的36行改成这样:

1
$(gen_subst_func "${nn%%:*}" "${nn##*:}" "subst_func_$i")

Shell会抱怨找不到subst_func_*函数,这是因为bash会 fork一个子进程用来执行括号中的命令,所以gen_subst_func 建立的subst_func_*函数存在于子进程中,gen_subst_func 函数一返回就消失了——这里有详细说明。