You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用text/template渲染「模板的模板」,仅渲染指定动作无需逐个转义

Go text/template 渲染「模板的模板」:仅渲染指定动作,其余保留字面量

问题场景

给定如下模板,需要仅渲染{{.Foo}},其余所有{{...}}语法均作为字面量保留:

I want to render {{.Foo}}.

but I don't want to render anything on this line, like {{.Bar}} or this template: [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}

Render {{.Foo}} again.

期望输出:

I want to render foo.

but I don't want to render anything on this line, like {{.Bar}} or this template: [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}

Render foo again.

实现方案

方案1:自定义模板函数简化转义

手动逐个编写{{ "{{" }}转义过于繁琐,可以自定义一个模板函数自动生成原始模板语法:

package main

import (
	"bytes"
	"text/template"
)

// raw 将传入的字符串包装为{{...}}字面量输出
func raw(s string) string {
	return "{{" + s + "}}"
}

func main() {
	// 注册自定义函数
	funcMap := template.FuncMap{"raw": raw}
	// 修改模板,将不需要渲染的片段用raw函数包裹
	tmplStr := `I want to render {{.Foo}}.

but I don't want to render anything on this line, like {{raw ".Bar"}} or this template: [{{raw ".Status | toUpper"}}{{raw "if eq .Status \"firing\""}}:{{raw ".Alerts.Firing | len"}}{{raw "end"}}] {{raw ".CommonLabels.alertname"}} for {{raw ".CommonLabels.job"}}

Render {{.Foo}} again.`

	tmpl, err := template.New("t").Funcs(funcMap).Parse(tmplStr)
	if err != nil {
		panic(err)
	}

	data := map[string]interface{}{"Foo": "foo"}
	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, data); err != nil {
		panic(err)
	}
	print(buf.String())
}

这种方式只需将无需渲染的模板内容传入raw函数,即可自动保留为{{...}}字面量。

方案2:自定义外层模板分隔符(推荐)

如果场景允许,建议给外层需要渲染的内容换用自定义分隔符(比如<<...>>),彻底避免语法冲突:

package main

import (
	"bytes"
	"text/template"
)

func main() {
	// 使用<<>>作为外层模板的分隔符
	tmplStr := `I want to render <<.Foo>>.

but I don't want to render anything on this line, like {{.Bar}} or this template: [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}

Render <<.Foo>> again.`

	// 解析模板时指定自定义分隔符
	tmpl, err := template.New("t").Delims("<<", ">>").Parse(tmplStr)
	if err != nil {
		panic(err)
	}

	data := map[string]interface{}{"Foo": "foo"}
	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, data); err != nil {
		panic(err)
	}
	print(buf.String())
}

这种方式无需任何转义操作,外层标记被正常渲染,内层{{...}}完全保留为字面量,逻辑更清晰。

方案3:嵌套模板的局限性说明

你提到的{{template "outer" .Foo}}方式不适用于当前场景:Go模板会解析所有{{...}}标记,未转义的非目标片段(比如{{.Bar}})会被尝试渲染,若数据中无对应字段会报错或输出空值,无法直接保留为字面量。

关于代码异味的讨论

这种「模板的模板」场景本身不一定是代码异味,需根据需求判断:

  • 若仅需简单变量替换,使用字符串替换(如strings.ReplaceAll)更轻量,无需引入模板引擎;
  • 若涉及复杂逻辑(条件判断、循环、函数调用等),使用模板引擎更合适;
  • 若频繁生成模板类内容,推荐使用自定义分隔符方案,既能利用模板能力,又能避免语法混淆,可读性和可维护性更好。

内容的提问来源于stack exchange,提问作者modulitos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 09:04:58