Ansible shell Module Tutorial + Examples


Percy Grunwald's Profile Picture

Written by Percy Grunwald

— Last Updated March 6, 2024

Ansible Course: Productive with Ansible (2024)
Ansible Course: Productive with Ansible (2024)
Go from Ansible beginner to Ansible pro with this full video course.

What does the Ansible shell module do?

Ansible’s shell module executes shell commands on remote hosts. By default, the shell module uses the /bin/sh shell to execute commands, but it’s possible to use other shells such as /bin/bash by passing the executable argument.

- name: create a text file in $HOME with the /bin/sh shell
  shell: echo "Hello, World!" > $HOME/test_file.txt

- name: read all text files in $HOME with the /bin/bash shell
  shell: cat < $HOME/*.txt
  args:
    executable: /bin/bash

Ansible shell Module Video Tutorial

If you prefer watching to reading, here’s a full video tutorial from the TopTechSkills YouTube channel covering a many of the points and examples from this article. Feel free to comment on this article or the video if you have any questions.

▶️ Watch on YouTube

When to use the shell module vs command module

The shell and command modules are almost identical, except that command cannot use special shell operators such as < > | ; & or access user-specific environment variables like $HOME. According to the docs, the command module is more secure and predictable, so my personal preference is to default to using command unless I need the extended functionality of shell.

# If your command looks like this, use `command`:
/path/to/program arg1 arg2

# If your command looks like this, use `shell`:
java -version 2>&1 | grep OpenJDK

How to sanitize variables for shell for maximum security and predictability

You should sanitize all variables you pass to the shell module with the quote filter to prevent command injection.

- name: create a text file with variable name that has been sanitized
  shell: echo "Hello, World!" > $HOME/{{ file_name | quote }}.txt

Examples

How to run shell only if a file doesn’t exist

Use the creates argument to run the shell command only if a file doesn’t exist already.

- name: create a text file in $HOME only if it doesn't already exist
  shell: echo "Hello, World!" > $HOME/test_file.txt
  args:
    creates: "$HOME/test_file.txt"

How to run shell only if a file does exist

Use the removes argument to run the shell command only if a file already exists.

- name: remove a text file in $HOME only if it already exists
  shell: rm $HOME/test_file.txt
  args:
    removes: "$HOME/test_file.txt"

How to run shell in a different directory

Use the chdir argument to run shell inside the specified directory.

- name: create a text file in $HOME by using chdir
  shell: echo "Hello, World!" > test_file.txt
  args:
    chdir: $HOME

How to run shell with custom environment variables

Use the environment keyword to set environment variables for the command. This is commonly used to alter the $PATH or set framework-specific ENVs such as RAILS_ENV.

- name: precompile assets with asdf in $PATH and $RAILS_ENV set to production
  shell: "rails assets:precompile"
  args:
    chdir: $HOME/my_app
  environment:
    PATH: "$HOME/.asdf/bin:$HOME/.asdf/shims:{{ ansible_env.PATH }}"
    RAILS_ENV: production

How to run shell multiple times in a loop

Use the loop keyword to run shell multiple times in a loop. Make sure you use quote to sanitize variables and prevent unexpected behavior.

- name: run shell with asdf in $PATH and $RAILS_ENV set to production
  shell: "{{ item | quote }}"
  args:
    chdir: $HOME/my_app
  environment:
    PATH: "$HOME/.asdf/bin:$HOME/.asdf/shims:{{ ansible_env.PATH }}"
    RAILS_ENV: production
  loop:
    - "rake db:migrate"
    - "rails assets:precompile"

How to use wildcards in shell

Wildcard characters like * are not valid in /bin/sh, which is the default shell executable for the shell module. If you want to use wildcards, you should use the /bin/bash shell by setting the executable argument:

- name: read all text files in $HOME with the /bin/bash shell
  shell: cat < $HOME/*.txt
  args:
    executable: /bin/bash

How to capture shell module output

Use the register keyword to capture the output of any commands you run.

- name: create a text file in $HOME with the /bin/sh shell
  shell: echo "Hello, World!" > $HOME/test_file.txt
  register: shell_output

- debug: var=shell_output

The debug task above will output the following:

ok: [123.123.123.123] => {
    "shell_output": {
        "changed": true,
        "cmd": "echo \"Hello, World!\" > $HOME/test_file.txt",
        "delta": "0:00:00.003421",
        "end": "2018-12-26 07:18:29.757159",
        "failed": false,
        "rc": 0,
        "start": "2018-12-26 07:18:29.753738",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "",
        "stdout_lines": []
    }
}

How to capture shell module output from a loop

When using the shell module in a loop, the output of each item in the loop will go into the results key of the registered variable.

- name: create a text file in $HOME with the /bin/sh shell
  shell: echo "Hello, World!" > $HOME/test_file_{{ item | quote }}.txt
  register: shell_output
  loop:
    - 1
    - 2

- debug: var=shell_output
ok: [123.123.123.123] => {
    "shell_output": {
        "changed": true,
        "msg": "All items completed",
        "results": [
            {
                ...
                "changed": true,
                "cmd": "echo \"Hello, World!\" > $HOME/test_file_1.txt",
                "delta": "0:00:00.003603",
                "end": "2018-12-26 07:28:48.819417",
                "failed": false,
                "invocation": {...},
                "item": "1",
                "rc": 0,
                "start": "2018-12-26 07:28:48.815814",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "",
                "stdout_lines": []
            },
            {
                ...
                "changed": true,
                "cmd": "echo \"Hello, World!\" > $HOME/test_file_2.txt",
                "delta": "0:00:00.003494",
                "end": "2018-12-26 07:28:49.129951",
                "failed": false,
                "invocation": {...},
                "item": "2",
                "rc": 0,
                "start": "2018-12-26 07:28:49.126457",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "",
                "stdout_lines": []
            }
        ]
    }
}

Further reading

Ansible shell Module on Ansible Docs

Comment & Share