Skip to main content
RunBook Academy

AnsibleX · YAML for Reliable AutomationYAML for reliable automation

Multiline values that survive

Intermediate⏱ ~15 minansible-playbook

What you'll learn

  • Distinguish literal and folded block scalars by what they do to newlines
  • Apply the strip, clip and keep chomping indicators correctly
  • Choose the right form for a file body, a long command and a long description
  • Predict the exact string a block scalar produces, including trailing newlines
  • Recognise the indentation rule that makes a block scalar lose content

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

Two characters decide what happens to the newlines in a multiline YAML value, and a third decides what happens at the end. Between them they produce five distinct results from identical-looking source, and the difference matters most for the two things people write multiline values for: file contents and shell commands.

IndicatorNewlines insideTrailing newlines
|keptexactly one (clip)
|-keptnone (strip)
|+keptall of them (keep)
>folded to spacesexactly one (clip)
>-folded to spacesnone (strip)

The same content, five ways

Rather than describe the difference, here is the result. <NL> marks each newline, and |END marks where the string stops.

Read-only / Safeidentical two-line bodies, five indicators
  vars:
  literal_clip: |
    line one
    line two
  literal_strip: |-
    line one
    line two
  literal_keep: |+
    line one
    line two

  folded_clip: >
    line one
    line two
  folded_strip: >-
    line one
    line two
Read-only / Safeexecuted against ansible-core 2.21.3
$ ansible-playbook -i localhost, multiline.yml
ok: [localhost] => (item=literal_clip  (|) ) => {
  "msg": "literal_clip  (|)  -> line one<NL>line two<NL>|END"
}
ok: [localhost] => (item=literal_strip (|-)) => {
  "msg": "literal_strip (|-) -> line one<NL>line two|END"
}
ok: [localhost] => (item=literal_keep  (|+)) => {
  "msg": "literal_keep  (|+) -> line one<NL>line two<NL><NL>|END"
}
ok: [localhost] => (item=folded_clip   (>) ) => {
  "msg": "folded_clip   (>)  -> line one line two<NL>|END"
}
ok: [localhost] => (item=folded_strip  (>-)) => {
  "msg": "folded_strip  (>-) -> line one line two|END"
}

Read the |+ result carefully: it kept two newlines, because the source had a blank line after line two before the next key. That is the “keep” behaviour doing exactly what it says, and it is why |+ is rarely what you want — the value now depends on how many blank lines a colleague left in the file.

Where each one is correct

| for a file body

A configuration file, a message of the day, an SSH key, a systemd unit fragment. Files end with a newline; | gives you exactly one.

Configuration changean inline file body
- name: Install the login banner
ansible.builtin.copy:
  dest: /etc/motd
  owner: root
  group: root
  mode: '0644'
  content: |
    This system is managed by Ansible.
    Local changes will be overwritten on the next run.
    Contact: ops@example.com

|- for a value that must not end in a newline

A password, a token, a single-line value that happens to be long, an argument to something that will not strip whitespace for you.

Read-only / Safea long single-line value
  vars:
  backend_url: >-
    https://metrics.internal.example.com/api/v2/write
    ?org=platform&bucket=fleet&precision=s

Note that this uses >-, not |-: the folded form joins the two source lines. Which brings us to the trap in the folded style.

> folds — including where you did not want it to

Service impact possiblea multi-line shell command, done properly
- name: Roll the logs and reload rsyslog
ansible.builtin.shell:
  cmd: |
    set -e
    logrotate -f /etc/logrotate.d/app
    systemctl reload rsyslog
  executable: /bin/bash
changed_when: true

> for prose that has to fit in a line-length limit

Long description, fail_msg, msg and name values. The folded form lets you wrap at 80 columns in the file and produce one line in the output.

Read-only / Safea wrapped message
- name: Refuse to proceed on an unrecognised environment
ansible.builtin.assert:
  that:
    - target_env in ['dev', 'staging', 'prod']
  fail_msg: >-
    target_env is {{ target_env | default('UNDEFINED') }}, which is not
    one of dev, staging or prod. Check the inventory group this host
    belongs to before rerunning.

The indentation rule

A block scalar’s content indentation is set by its first non-empty line. Everything is measured from there, and anything less indented ends the block.

There are two outcomes, and only one of them is loud.

Read-only / Safeloud: a line less indented than the baseline
content: |
    first line
second line
Read-only / Safesilent: a line more indented than the baseline
content: |
line one
  line two
line three

That second block produces 'line one\\n line two\\nline three\\n' — two leading spaces on the middle line, permanently, in the file you write. For a config format that treats indentation as meaningful, or a shell command where a stray indent is harmless, it does not matter. For a key file, a checksum manifest or an /etc/hosts fragment, it does.

The reliable habit is to indent every line of the block by exactly one level from the key, and to let nothing vary from there unless the content genuinely wants it. yamllint catches most of these; lesson 6 covers it.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A task uses ansible.builtin.shell with a folded block scalar containing three commands on three source lines. What runs on the managed node?

  2. Q2. Which of these produce a value with no trailing newline at all? Select all that apply.

  3. Q3. A blank line inside a folded block scalar survives as a real newline rather than being folded into a space.

  4. Q4. A copy task with an inline literal content block writes a file in which one line has two unexpected leading spaces. What happened?

Passing score: 75%. Answers are checked in this browser.