Table of Contents
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.
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 ENV
s 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