Working With Strings
This section is about transforming data once it’s already in a variable — cleaning up a string, reshaping a list, merging two dictionaries, filling in a sensible default when something’s missing. We’re covering the general-purpose tools you’ll reach for constantly, across every data type Ansible works with.
We’re deliberately not covering the specialized filters for things like IP addresses, MAC addresses, Kubernetes resources, or UUID generation — those exist, they’re documented, and if you ever need one, the official filters guide and the complex data manipulation guide cover them well. This section is about the everyday tools, not the full catalog.
We start with strings.
Recap: Case Conversion
upper and lower already showed up back in the Jinja2 basics chapter:
msg: "{{ 'hello' | upper }}"HELLOTwo more worth knowing: capitalize (uppercases just the first character, lowercases the rest) and title (uppercases the first letter of every word):
msg: "{{ 'hello world' | capitalize }}"Hello worldmsg: "{{ 'hello world' | title }}"Hello WorldTrimming Whitespace With trim
msg: "{{ ' hello ' | trim }}"helloLeading and trailing whitespace gone — genuinely useful for values that came from somewhere less predictable than a value you typed yourself, like a registered command’s output.
Replacing Text With replace
msg: "{{ 'hello world' | replace('world', 'there') }}"hello thereA plain, literal substitution — every occurrence of the first argument gets swapped for the second.
Pattern Matching With regex_replace And regex_search
For anything beyond a literal substring, regex_replace and regex_search bring real regular expressions into play:
msg: "{{ 'app-2026-08-23.log' | regex_replace('\\d{4}-\\d{2}-\\d{2}', 'DATE') }}"app-DATE.logmsg: "{{ 'port: 8080' | regex_search('\\d+') }}"8080Note
regex_search returns nothing at all (None) if the pattern doesn’t match — not an error, just an empty result. Combined with default, covered later in this section, that’s exactly how you handle a pattern that might not always be present without the playbook failing outright.
Quoting Values Safely With quote
quote wraps a string in shell-safe quoting, escaping anything that would otherwise be interpreted as special by a shell:
msg: "{{ \"it's a test\" | quote }}"'it'"'"'s a test'This matters most when a variable’s value is heading into a command or shell task — exactly the same concern as safely quoting a variable in a shell script: an unquoted value containing spaces or special characters can silently change what actually gets executed. If you’re building a shell command string from a variable you don’t fully control, quote is the filter doing the job a careful shell script would do with its own quoting discipline.
Measuring Length With length
msg: "{{ 'hello' | length }}"5length isn’t string-specific — it works on lists and dictionaries too, both covered later in this section, and reports the count of elements or keys respectively.
Concatenation With ~
Jinja2 uses ~, not +, to join values into a string:
vars:
name: "Alice"
age: 30
tasks:
- name: Greet
ansible.builtin.debug:
msg: "{{ 'Hello, ' ~ name ~ '! You are ' ~ age ~ ' years old.' }}"Hello, Alice! You are 30 years old.Notice age, a number, got joined in directly alongside plain strings — ~ converts everything to a string automatically before joining.
A Gotcha: + Doesn’t Do What You Might Expect
msg: "{{ 'Hello, ' + name }}"If name is a genuine string, this works. But swap in a number, or anything that isn’t already a string, and + fails outright — it expects both sides to already be the same type, and won’t convert one for you the way ~ does. The practical rule: use ~ for building strings out of mixed values, and reserve + for numeric addition or actual list concatenation — covered in the next chapter, where + does something completely different and entirely correct.
Best Practices
- Use
~for string concatenation, not+— it converts non-string values automatically, and won’t fail the moment a number shows up in the mix. - Reach for
quoteany time a variable’s value is heading into acommandorshelltask, especially if that value didn’t come from something you typed yourself. - Pair
regex_searchwithdefault(covered later in this section) whenever the pattern genuinely might not match — an empty result isn’t an error, but using it without a fallback can lead to one further downstream.