Template variable replacements only. Is text/Template a good fit?

669 views Asked by At

I'm looking for an efficient way to replace a bunch of placeholders/tokens in a user supplied text file, with their corresponding values stored in a simple map or environment vars. The thing is that the template file will be supplied by the end user, so I'm looking for a "safe" way to do only the variable replacements, without any risk of code execution, etc.

Go's standard "text/template" would work for the replacement itself but imposes specific formatting requirements (e.g. dot "." before the Key) and opens up other possibilities with its function invocations, pipelines, etc.

So what I'm looking for, ideally, is a function that can parse a text file with configurable delimiters ("{{}}" or "${}" or "$##$") and replace all the detected tokens with lookups into a supplied map or their env var values. Similar to what Python's string.Template (https://docs.python.org/2.6/library/string.html?highlight=string.template#string.Template) does.

Is there an easy way to configure or reuse the text/template library for this? Are there any other approaches that would fit the use case better? I've looked into non-golang options as well (like envsubtr, awk and sed scripts etc.) so feel free to go outside of Go if something fits better.

Sample input file ('template.properties'):

var1=$#VAR_1#$
var2=$#VAR_2#$

Sample input data:

VAR_1 = apples
VAR_2 = oranges

Expected output after processing:

var1=apples
var2=oranges
2

There are 2 answers

4
Ed Morton On

This will work as long as your variable names don't contain ERE metacharacters:

$ cat tst.awk
NR==FNR { var2val[$1] = $NF; next }
{
    for (var in var2val) {
        sub("[$]#"var"#[$]",var2val[var])
    }
    print
}

$ awk -f tst.awk input.data template.properties
var1=apples
var2=oranges

wrt your comment below about having the mappings in variables instead of in input.data, this might be what you're looking for:

$ cat tst.awk
BEGIN {
    split(vars,tmp)
    for (i in tmp) {
        var2val[tmp[i]] = ENVIRON[tmp[i]]
    }
}
{
    for (var in var2val) {
        sub("[$]#"var"#[$]",var2val[var])
    }
    print
}

will work with shell variables like:

$ VAR_1=apples VAR_2=oranges gawk -v vars="VAR_1 VAR_2" -f tst.awk template.properties
var1=apples
var2=oranges

or:

$ export VAR_1=apples
$ export VAR_2=oranges
$ gawk -v vars="VAR_1 VAR_2" -f tst.awk template.properties
var1=apples
var2=oranges

or:

$ VAR_1=apples
$ VAR_2=oranges
$ VAR_1="$VAR_1" VAR_2="$VAR_2" gawk -v vars="VAR_1 VAR_2" -f tst.awk template.properties
var1=apples
var2=oranges

Note that this is gawk-specific due to ENVIRON and requires VAR_1 etc. to be exported or set on the command line as I have it above.

Or maybe this is what you want:

$ cat tst.awk
BEGIN {
    var2val["VAR_1"] = VAR_1
    var2val["VAR_2"] = VAR_2
}
{
    for (var in var2val) {
        sub("[$]#"var"#[$]",var2val[var])
    }
    print
}

$ VAR_1=apples
$ VAR_2=oranges
$ awk -v VAR_1="$VAR_1" -v VAR_2="$VAR_2" -f tst.awk template.properties
var1=apples
var2=oranges
0
valyala On

Just use fasttemplate[1]. It perfectly fits your requirements:

  • Arbitrary placeholders' start and end delimiters can be used.
  • Zero risk for untrusted input, because there is no any logic except placeholders' substitution.
  • Works much faster than text/template (by 10x).

[1]https://github.com/valyala/fasttemplate