I have a string and there are multiple substrings that will need to be replaced in it.
The string:
string1 = "I want to fly tomorrow"
I will need to take whatever that needs to replaced from a map.
The map:
map1 = {
"I": "we"
"want": "do not want"
"tomorrow": "today"
}
So the keys in the map1
map are what need to be replaced in the string1
string and the values should be the new values in the string.
The result should look something like this:
we do not want to fly today
I have been thinking of a solution but I am not even close.
I tried this:
try = [for replacement in keys(local.map1): replace(local.string1, replacement, local.map1[replacement])]
but this returns a list of strings that each have only one value replaced.
NOTE: string1
and map1
are just examples and they could have any other values so I am looking for a general solution please :)
This is something that was needed for work; this was tested on Terraform 0.12.23. Different from Marcin's answer in the sense that this works for placeholders that are right next to each other, in between characters (such as quotes), or otherwise not split by spaces.
Given we have a file with {}
formatted placeholders, I needed to replace them with a map of key/value pairs.
Here's what you can copy/paste.
sample.txt
Hello {name}, my favorite food is {food} I'd
like to eat {food} and sleep.
Here's a {non_existent} variable
playground.tf
locals {
input = file("./sample.txt")
map = {
food = "Sushi"
name = "Data_sniffer"
}
out = join("\n", [
for line in split("\n", local.input) :
format(
replace(line, "/{(${join("|", keys(local.map))})}/", "%s"),
[
for value in flatten(regexall("{(${join("|", keys(local.map))})}", line)) :
lookup(local.map, value)
]...
)
])
}
output "test_out" {
value = local.out
}
Output
$ terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
test_out = Hello Data_sniffer, my favorite food is Sushi I'd
like to eat Sushi and sleep.
Here's a {non_existent} variable
Here's an explanation of how this magic works:
The first step is to split up your text by newlines, so you'll have the following to work with:
[
"Hello {name}, my favorite food is {food} I'd",
" like to eat {food} and sleep.",
" Here's a {non_existent} variable"
]
Now we want to replace our placeholders. However, at the time of writing this, Terraform doesn't have an easy way to replace multiple string easily in one string. So we'll use the format
function in the meantime; which requires %s
for its placeholders. Basically, we want our array to now look like this:
[
"Hello %s, my favorite food is %s I'd",
" like to eat %s and sleep.",
" Here's a {non_existent} variable"
]
In order to get this conversion for each line, we want to replace our valid {}
placeholders with %s
; i.e. we don't want to replace {non_existent}
which doesn't exist in our map.
We take advantage of the fact that Terraform allows you to do regex replacements, so we need the following regex: {(food|name)}
(where food
and name
are the keys that we want to replace). Since we don't want to manually maintain this list, we build the regular expression programmatically with keys()
and string interpolation.
"/{(${join("|", keys(local.map))})}/"
Now that we have a regex, we can use replace
, which supports regular expressions.
replace(line, "/{(${join("|", keys(local.map))})}/", "%s")
But wait! All we have now are %s
, how are we going to know what to replace in what order??? That's where regexall()
comes in (not regex()
). Using almost the same regex above, we'll pull out the matching placeholders once again.
(The only difference between the regex above and this one is the lack of /
at the beginning and ending)
regexall("{(${join("|", keys(local.map))})}", "Hello {name}, my favorite food is {food} I'd")
This function call will return all of our found placeholders. It's important to use regexall()
all here or else it'll stop at the first match.
[
[
"name"
],
[
"food"
]
]
We don't need the doubly nested lists here, so we'll use flatten()
.
flatten(
regexall("{(${join("|", keys(local.map))})}", "Hello {name}, my favorite food is {food} I'd")
)
Which now will return,
[
"name",
"food",
]
Now, we can use this list to lookup what the respective values would be from the map with the lookup()
function.
[
for value in flatten(regexall("{(${join("|", keys(local.map))})}", "Hello {name}, my favorite food is {food} I'd")) :
lookup(local.map, value)
]
Which returns:
[
"Data_sniffer",
"Sushi",
]
This array of replacement values can then be fed into our top-level format()
call. However, format()
doesn't accept an array it expects comma-separated arguments. So use the ...
operator to expand the list into function arguments.