Linux Shell管道调用用户定义函数(使shell支持map函数式特性)
Linux中有一个管道的概念,常用来流式的处理文本内容,比如一个文件对其中的每一行应用好几个操作,出于两个方面的考虑可能需要在管道中使用用户定义函数:
1. 刚需: 内置的sed/awk之类的可能没法满足我们的需求,只能使用用户定义函数
2. 代码质量: 如果是流式操作很多很长,那么可能就需要将其进行拆分,将相关的部分封装为一个函数,然后流式调用函数,这样程序的可读性更好,也更容易维护
在管道中上一个程序的标准输出会被放到下一个程序的标准输入,处在管道中的程序需要做的就是读取标准输入中的东西进行处理。
下面是一个在管道中使用用户定义函数的例子,print函数不断地从标准输入流中读取数据然后放到标准输出流中:
#! /bin/bash print(){ while read line do echo "$line" done } cat original.data | print
上面的例子是比较简单的,那么现在需求又改了,假设读入的每行都是一个数字,需要对数字加1并保存到一个新的文件,那么对上面的脚本进行扩充:
#! /bin/bash add(){ while read line do echo $(($line+1)) done } save(){ while read line do echo $line >> result.data done } print(){ while read line do echo "$line" done } # cat original.data | print cat original.data | add | save
上面的脚本虽然功能实现了,但是有冗余代码,在函数add和save中重复了循环读取输入流数据的代码,现在重构一下,将重复读取的部分抽象出来为一个新的函数map,此函数接受一个可以处理单行的函数的名字function_name,map函数每读取一行就调用function_name函数,将读取到的内容作为参数传入,如果处理单行的函数function_name还有输出,则还可以作为下一个管道命令的输入,这个处理模型类似于函数式编程中的map的概念,即这里通过一个小技巧使得shell支持部分函数式的功能。
现在按照上面的思想来对脚本进行改造:
#! /bin/bash add(){ echo $(($1+1)) } save(){ echo $1 >> result.data } print(){ echo "$1" } map(){ function_name=$1 while read line do $function_name "$line" done } # cat original.data | print cat original.data | map "add" | map "save"
现在看起来好多了,并且程序的可读性也得到了保障,但是如果我想对add传入一个参数指定究竟要加几怎么办呢,又或者我想在save函数自定义保存到位置又该怎么办呢?
这个很简单,在map中取完第一个参数之后就没再用了,直接shift参数然后直接传递即可,再重构一下代码:
#! /bin/bash add(){ echo $(($1+$2)) } save(){ echo $1 >> $2 } print(){ echo "$1" } map(){ function_name=$1 shift while read line do $function_name "$line" $@ done } # cat original.data | print cat original.data | map "add" 2 | map "save" "result.data"
看起来似乎很完美了,但还能做得更好吗?
如果map方法需要在其它地方调用怎么办?一种方法是将map抽取为utils.sh或者functional.sh之类的库,然后在使用的时候引入它即可,比如新建functional.sh的文件:
################################################## # # # shell 函数式库 # # ################################################## # $1 函数名 # $[2,] 传递给$1函数的参数 map(){ function_name=$1 shift while read line do $function_name "$line" $@ done }
使用的时候source functional.sh即可使用map函数。
或者更细粒度的,直接将map抽取为一个文件(以后如果有其它的类似函数也都会抽取为单独的文件),并且放到PATH中,这样使用的时候无需引入,直接就可以使用,比如创建一个文件叫做map:
################################################## # # # functional.map # # ################################################## # $1 函数名 # $[2,] 传递给$1函数的参数 map(){ function_name=$1 shift while read line do $function_name "$line" $@ done } map #@
但是这种方式有个陷阱就是一定要记得每次使用时保证map在$PATH中。
上面就是一步一步优化封装了一个小小的map函数使得shell支持map特性,虽然shell是一门面向字符串的语言,但是稍微对其封装就可以使其具备一些高级语言的特性,提高开发效率,使得程序可读性更好。
.