183

right now I am using a shell script in ansible that would be much more readable if it was on multiple lines

- name: iterate user groups
  shell: groupmod -o -g {{ item['guid'] }} {{ item['username'] }} ....more stuff to do
  with_items: "{{ users }}"

Just not sure how to allow multiline script in Ansible shell module

Subtubes
  • 15,851
  • 22
  • 70
  • 105

7 Answers7

402

Ansible uses YAML syntax in its playbooks. YAML has a number of block operators:

  • The > is a folding block operator. That is, it joins multiple lines together by spaces. The following syntax:

    key: >
      This text
      has multiple
      lines
    

    Would assign the value This text has multiple lines\n to key.

  • The | character is a literal block operator. This is probably what you want for multi-line shell scripts. The following syntax:

    key: |
      This text
      has multiple
      lines
    

    Would assign the value This text\nhas multiple\nlines\n to key.

You can use this for multiline shell scripts like this:

- name: iterate user groups
  shell: |
    groupmod -o -g {{ item['guid'] }} {{ item['username'] }} 
    do_some_stuff_here
    and_some_other_stuff
  with_items: "{{ users }}"

There is one caveat: Ansible does some janky manipulation of arguments to the shell command, so while the above will generally work as expected, the following won't:

- shell: |
    cat <<EOF
    This is a test.
    EOF

Ansible will actually render that text with leading spaces, which means the shell will never find the string EOF at the beginning of a line. You can avoid Ansible's unhelpful heuristics by using the cmd parameter like this:

- shell:
    cmd: |
      cat <<EOF
      This is a test.
      EOF
slm
  • 15,396
  • 12
  • 109
  • 124
larsks
  • 277,717
  • 41
  • 399
  • 399
  • What if using `ansible` instead of `ansiable-playbook`? – Archon Nov 21 '22 at 09:43
  • Using `shell: >` actually results in ansible inserting line breaks, so this is incorrect. – Philipp Ludwig Jan 20 '23 at 15:43
  • 2
    Nowhere in this answer do I show an example of using `shell: >`, and furthermore your statement is simply incorrect: Ansible doesn't know whether you use `"` or `>` or `|`; it only knows what text it receives after parsing the YAML. The `>` quote operating is a folding operator and does not preserve newlines. If you're seeing different behavior, feel free to open a question and I would be happy to take a look at it. – larsks Jan 20 '23 at 23:35
  • There's a slight gotcha about the indentation of the rows beneath `>`: they all must be indented the same amount for the folding to work correctly (at least in Ansible). I posted a complementary answer about this. This might be what @PhilippLudwig was seeing? – JK Laiho Mar 23 '23 at 14:22
26

Tried with ansible 2.0.0.2:

---
- hosts: all
  tasks:
    - name: multiline shell command
      shell: >
        ls --color
        /home
      register: stdout

    - name: debug output
      debug: msg={{ stdout }}

The shell command is collapsed into a single line, as in ls --color /home

Reference (visited in 2021): https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html ==> search form "multiple lines" in the page.

Marcello Romani
  • 2,967
  • 31
  • 40
  • 4
    Yeah but in the shell `>` has a very specific meaning. I tried this and it did not work as expected. – Subtubes Oct 25 '16 at 04:16
  • 6
    That is why it's just in the first line, not in the subsequent ones. It worked fine for me with ansible 2.0 as I wrote, although it didn't print out the full ls output with ansible 1.9.4. What version of Ansible did you use? – Marcello Romani Oct 25 '16 at 09:00
  • @MarcelloRomani It does not work with ansible 2.10.8. – Philipp Ludwig Jan 20 '23 at 15:44
7

Adding a space before the EOF delimiter allows to avoid cmd:

- shell: |
    cat <<' EOF'
    This is a test.
    EOF
Id2ndR
  • 113
  • 1
  • 6
6

I prefer this syntax as it allows to set configuration parameters for the shell:

---
- name: an example
  shell:
    cmd: |
      docker build -t current_dir .
      echo "Hello World"
      date

    chdir: /home/vagrant/
alfredocambera
  • 3,155
  • 34
  • 29
2

A quick caveat about the folding > operator that had me scratching my head for a while: the rows indented beneath it have to be at the same indentation level for the folding to happen correctly, at least when using Ansible.

I was converting an existing multi-line shell command that had \ line endings to escape newlines and had indented every row below the first one two spaces more. I used shell: >, removed the backslashes and preserved the existing indentation.

The newlines were preserved, and I was losing my mind, because everywhere it said that > should convert them to spaces.

See below for an example:

# Converts into ls -la /tmp, as expected
- name: working shell command that folds newlines
  shell: >
    ls
    -la
    /tmp

# Tries to run -la and /tmp as their own commands
- name: failing shell command that does not fold newlines
  shell: >
    ls
      -la
      /tmp

Hope this helps someone in a similar situation!

JK Laiho
  • 3,538
  • 6
  • 35
  • 42
1

You can use lineinfile collection to append the file, attribute create to create file if not exist and multi line string using "|".

- name: Add a line to a file if the file does not exist
  ansible.builtin.lineinfile:
    path: ~/.ssh/config
    line: |
      Host {{item.COMPONENT}} {{item.COMPONENT}}.{{ZONE}}
        HostName {{r53_var_one.resource_record_sets[0].resource_records[0].value}}
        User centos
        Port 22
        IdentityFile ~/.ssh/key.pem
        StrictHostKeyChecking no
    create: yes
    mode: '0600'
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
1

To know how to multilines works in ansbile, you may want to test this playbook to actually know what pipe (LF->LF) and Greather than (LF->space) are about.

- hosts: all,localhost
  tasks:
   - name: greather_than
     debug:
      msg: >
        a
        b
        c
   - name: pipe
     debug:
      msg: |
        a
        b
        c

This is tested this way.

# ansible-playbook test.yml
 [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [all,localhost] ***************************************************************************

TASK [Gathering Facts] *************************************************************************
ok: [localhost]

TASK [greater_than] ****************************************************************************  
ok: [localhost] => {
    "msg": "a  b c\n"
}

TASK [pipe] ***********************************************************************************
ok: [localhost] => {
    "msg": "a \nb\nc\n"
}

 PLAY RECAP  ***********************************************************************************
 localhost                  : ok=3    changed=0    unreachable=0    failed=0       skipped=0    rescued=0    ignored=0

# 
MUY Belgium
  • 2,330
  • 4
  • 30
  • 46