我正在创建一个完全基于sed的原始实验模板引擎(仅供我私人享受)。我现在想要实现几个小时的一件事就是用它们包含的命令的输出替换某些文本模式。
要清除,如果输入行看起来像这样
Lorem {{echo ipsum}}
我认为sed输出看起来像这样:
Lorem ipsum
我最接近的是:
echo 'Lorem {{echo ipsum}}' | sed 's/{{\(.*\)}}/'"$(\\1)"'/g'
哪个不起作用。
然而,
echo 'Lorem {{echo ipsum}}' | sed 's/{{\(.*\)}}/'"$(echo \\1)"'/g'
给了我
Lorem echo ipsum
我不太明白这里发生了什么。为什么我可以对echo命令进行反向引用,但是无法评估$()中的整个反向引用?什么时候被评估?我试图通过纯sed实现的目标是什么?
请记住,我完全清楚,通过其他工具可以轻松实现我想要实现的目标。但是,我对纯sed是否有可能非常感兴趣。
谢谢!
答案 0 :(得分:3)
您的尝试无效的原因是$()
在sed
被调用之前被shell扩展。出于这个原因,它无法使用后向引用sed
最终将被捕获。
使用GNU sed可以做这种事情(不是用POSIX sed)。主要技巧是GNU sed对e
命令有一个s
标志,它使模式空间(整个空间)替换为作为shell命令执行的模式空间的结果。这意味着什么
echo 'echo foo' | sed 's/f/g/e'
打印goo
。
这可以用于您的用例,如下所示:
echo 'Lorem {{echo ipsum}}' | sed ':a /\(.*\){{\(.*\)}}\(.*\)/ { h; s//\1\n\3/; x; s//\2/e; G; s/\(.*\)\n\(.*\)\n\(.*\)/\2\1\3/; ba }'
sed
代码的工作原理如下:
:a # jump label for looping, in case there are
# several {{}} expressions in a line
/\(.*\){{\(.*\)}}\(.*\)/ { # if there is a {{}} expression,
h # make a copy of the line
s//\1\n\3/ # isolate the surrounding parts
x # swap the original back in
s//\2/e # isolate the command, execute, get output
G # get the outer parts we put into the hold
# buffer
s/\(.*\)\n\(.*\)\n\(.*\)/\2\1\3/ # rearrange the parts to put the command
# output into the right place
ba # rinse, repeat until all {{}} are covered
}
这样可以在正则表达式中使用sed
的贪婪匹配来始终捕获一行中的最后一个{{}}
表达式。请注意,如果一行中有多个命令,而后一个命令中有一个命令具有多行输出,则会有困难。处理这种情况需要定义一个标记,即不允许嵌入在数据中的命令作为其输出的一部分,并且不允许包含模板。我会建议{{{}}}
之类的东西,这会导致
sed ':a /\(.*\){{\(.*\)}}\(.*\)/ { h; s//{{{}}}\1{{{}}}\3/; x; s//\2/e; G; s/\(.*\)\n{{{}}}\(.*\){{{}}}\(.*\)/\2\1\3/; ba }'
这背后的原因是,如果嵌入式命令进一步打印{{}}
条款,模板引擎无论如何都会遇到麻烦。这个约定是不可能强制执行的,但是传入这个模板引擎的任何代码最好都来自可靠的源代码。
请注意,我不确定这整件事是否理智 1 。您不打算在任何类型的生产代码中使用它,是吗?
1 但是,我非常确定是否这是一个理智的想法。