sed是好玩具。
稍有常识的运维工作者都能写得一手好替换,宛然一副ed编辑器的过来人,比如将redis-server的监听端口设为6679:
sed -i -e 's/^port.*/port 6679/' $REDIS_DIR/conf/redis.conf
或者对单行编辑命令用得滚瓜烂熟,比如对工程下的所有py文件第二行添加# coding=utf8
:
find $PYTHON_PROJECT -name '*.py' -print0 | xargs -0I{} sed -i -e '2i\
# coding=utf8
' {}
不过这些还是too simple,让我们来一点面向多行编辑的sed编程。
1.tail -n k
也就是输出文本文件的倒数k行。
利用sed的hold空间是很容易写出一个解答的:
k=11; sed -En 'H; '$k',${g; s/[[:print:]]*\n//; h;}; $p;' filename
总之就是充分利用hold空间来做文章:先追加到hold空间,再交换出来进行处理,再放回去。
如果抽象能力强一点的读者,可能会直接一眼抽象出这里的sed范式:
[address]{[address]h; [address]H; [address]{};}
或者标签党会倾向于直接写循环:
k=11; sed -En ':next; N; $p; '$k',$D; $!b next' filename
不停读取下一行追加到当前空间,并且在11行以后删除当前空间的首行文本,直到EOF打印。
当然这里的范式也是很容易看出来的:
[address]{:label; N; [address]{}; [address]b label;}
接下来我们来看这两个范式能够做一些什么事情。
2.将所有花括号包围的文本行都倒置
先看第一范式,注意到循环不变式是hold空间里的始终是已倒序的文本后其实是信手拈来:
sed -En '/\{/,/}/{/\{/h; /\{/!H; x; s/(.+)\n(.+)/\2\n\1/; h; /}/p;};' filename
如果用第二范式的话,其实也是非常自然的:
sed -En '/\{/{:next; N; s/(.+)\n(.+)/\2\n\1/; /}/!b next;}; p;' filename
3.分界不明显的情况
比如对py文件中的def函数定义块以行为单位进行倒置,情况会棘手一些。
在这种情况下,对每行的输入进行控制变得必要起来,不能再依靠sed进行文件读取。
也就是说整个读取-输入循环,完全要由我们自己来进行驱动。第一范式直接出局。
来看第二范式需要进行怎样的修改:
sed -En ':next; N; /^def/!s/.+\n//; /def/{:inner; N; /def.+def/!{s/(.+)\n(.+)/\2\n\1/; b inner;}; /def.+def/{h; s/(.+\n).+/\1/; p; g; s/.+\n//;};}; $!b next;' filename
好吧这看起来确实有点吓人了,不过简单来说,它其实就是:
:label1; N; [address]{:label2; N; [address]{}; [address]b label2;}; [address]b label1;```
其实也就是在第二范式上增加了一层供我们手动控制输入的循环而已。
不过这样就提供给了我们能够完全控制文本输入并定义其行为的能力,sed文本处理能力得以大大提高。
---
好吧我知道这种艰难生涩的文章是没有人会读的,我也被sed的坑折磨得欲仙欲死,我用了一个国庆节的下午才发现macOS的sed是不兼容POSIX sed的,还有奇奇怪怪的POSIX正则表达式的坑,我对sed的热情都消失殆尽了,再见(#微笑).
不过讲道理,sed真的很好玩啊,goto大法好,微笑。