Ansible core provides hundreds of Ansible modules for almost all use cases. You can find a comprehensive list of all the Ansible modules along with their description on the Ansible documentation page.
Sometimes, all you need is to execute commands directly on target hosts as you would on a bash shell. This is where the Ansible shell module comes in handy.
In this guide, you're going to learn about the Ansible shell module, how it works and how you can use it to execute commands against managed nodes.
What is the Ansible Shell module?
The Ansible shell module is a handy module that allows you to directly execute commands on the shell of remote targets, just as you would if you were logged in. By doing so, it helps maintain the originality of command execution.
The shell module is closely related to the command module. Both help achieve the same result. However, a few differences exist between the two:
- The shell module executes commands directly on the shell of target hosts. By default, the shell module uses the
/bin/sh
shell to run commands, although you can configure it to use other shells. With the command module, the executed commands are not processed through the shell. - Since commands are not executed on the shell, the command module does not support environment variables, pipes and other operators such as ‘>’ , ‘<’ , ‘&’, ‘;’ and ‘| |’. With shell module, piping, redirection and variables are fully supported. Thus, the shell module provides more flexibility.
- If running commands securely on target systems is your priority, use the command module. Unlike the shell module, the command module is not affected the remote user’s shell environment.
Ansible Shell Module vs Other Modules
The Ansible shell module falls in the same category as the command
, script
, and raw
modules. These are collectively referred to as run commands.
While efficient in getting things done fairly fast, run commands should only be used as a last resort. This is because they apply the least logic while executing tasks and the concept of the desired state is non-existent. Subsequent execution of the shell command might fail if a condition has already been met leading to errors.
In addition, catching errors is not possible with the shell module unless you register the result of the first command. You will then have to follow it up with a subsequent task in the playbook to implement a conditional logic to confirm if the error occurred and then deal with it. This can result in bottlenecks that considerably undermine automation.
For this reason, shell commands should be limited to carrying out simple tasks on remote systems. Where the desired state of services or applications is required, task-specific modules such as service
, copy
, file
, and lineinfile
, to name a few, are recommended. This makes playbooks more versatile and reusable.
Run your deployments in a scalable and cost-effective open cloud infrastructure. Cherry Servers' secure virtual private servers offer automatic scaling, flexible pricing, and 24/7 technical support. Our pre-defined Ansible module helps you automate the installation and configuration.
Prerequisites
To follow this guide, you should have:
- An Ansible control node. In this tutorial, we are running Ubuntu 20.04. In case you haven’t installed Ansible, follow our guide on How to Install and Configure Ansible on Ubuntu 20.04.
- A remote host against which you will run the commands.
Run Ansible Ad-hoc Shell Commands
The true power of Ansible lies in playbooks. However, playbooks are used to execute elaborate tasks on target hosts. Suppose you want to run commands really quick without saving them for later use. How would you go about this? This is where ad-hoc commands come in handy.
Ad-hoc commands are one-liner commands that you can run on the fly without the need for writing a playbook.
For instance, you might want to check the uptime or disk space utilization of your remote hosts. Instead of writing an entire playbook for such tasks, a better approach would be to run ad-hoc commands against your hosts.
Ad-hoc commands take the following syntax:
ansible [pattern] -m [MODULE] -a {COMMAND_OPTIONS}
The pattern specifies the host group that the target host belongs to. The -m
option specifies the type of module while the -a
option takes the command arguments.
Let us take a few examples.
To check the uptime of all the target hosts, run the command:
sudo ansible all -m shell -a 'uptime -p'
To check the memory usage, run:
sudo ansible all -m shell -a 'free -m'
To check disk space utilization of the host under the db_server group, execute:
sudo ansible db_server -m shell -a 'df -Th'
Run a Single Command With Ansible Shell Module
Aside from running ad hoc commands, the Ansible shell module is also used in playbooks to specify the tasks to be carried out on remote hosts.
Consider the playbook below.
---
- name: Shell module example
hsots: webservers
tasks:
- name: Check system information
shell:
"lsb_release -a"
register: os_info
- debug:
msg: "{{os_info.stdout_lines}}"
In this example, the playbook runs against a host group called webservers and executes an lsb_release -a
command which retrieves details about the OS version. The output is then saved in a register variable called os_info
.
The last line prints out the output stored in the os_info
variable to stdout using the debug
module.
Here is the output of the playbook execution.
Run a Command Using Shell Module If a File Does Not Exist
The creates
parameter allows you to run a command if a file does not exist. It specifies the path to the file which, if it exists, the command to be executed is skipped.
The playbook shown checks if the file hello.txt
exists in the home directory of the target host. If the file is absent, then the shell command specified is executed. If the file exists, then the shell command aborts.
---
- name: Create a file in the home directory if it doesn't exist
hosts: webservers
tasks:
- name: Create a file in the home directory
shell: echo "Hey guys!" > $HOME/hello.txt
args:
creates: "$HOME/hello.txt"
Since the file does not exist on the remote target, the shell command is successfully executed as you can see from the playbook execution.
The command below confirms that the hello.txt
file was created in the remote target’s home directory.
ssh root@173.82.120.115 "ls -l ~"
Run a Command Using Shell Module If a File Exists
The removes
parameter specifies the filename, and if the file exists, the command is executed. In the previous example, the hello.txt
file was created on the remote target’s home directory.
In this playbook, the removes
parameter checks if this file exists on the remote target. And since you already created it, the playbook proceeds to execute the shell command which removes the file.
---
- name: Remove a file in the home directory only if it exists
hosts: webservers
tasks:
- name: Remove a file in the home directory
shell: rm $HOME/hello.txt
args:
removes: "$HOME/hello.txt"
warn: false
The playbook execution confirms that the file was removed.
Run a Command in a Different Directory
To run a shell command inside a specific directory, use the chdir
parameter. The playbook below downloads the Apache binary file in the /usr/local/src path.
---
- name: Download Apache source file to /usr/local/src directory
hosts: webservers
tasks:
- name: Download Apache tarball file
shell: wget https://dlcdn.apache.org/httpd/httpd-2.4.52.tar.gz
args:
chdir: /usr/local/src
warn: false
The playbook confirms that the task was successfully carried out.
Use Ansible shell Module With Environment Variables
The shell module also enables you to set new environment variables. This is made possible using the environment
parameter. Consider the playbook below.
---
- name: Environment variable example
hosts: webservers
tasks:
- name: Ansible Shell module set an environment variable
shell: echo $NEW_VAR
register: command_result
envnironment:
NEW_VAR: "john_doe"
- debug:
msg: "{{ command_result.stdout_lines }}"
The playbook sets the NEW_VAR
environment variable to john_doe.
NOTE: The environment variable is only set for that particular task. In subsequent tasks, the NEW_VAR variable will not be available.
Run multiple Commands With Ansible Shell Module
So far, you have seen the shell module running single commands on managed hosts. You can also specify a set of commands to be carried in chronological order.
To achieve this, start off the shell command with a vertical bar, followed by a list of tasks to be carried out. In this example, the output of the date
, uptime
, and echo
command is saved to the date.txt
, uptime.txt
and hello.txt
files respectively which are then saved in the /tmp
directory.
---
- name: Shell module example
hsots: webservers
tasks:
- name: Run multiple commands
shell: |
date > /tmp/date.txt
uptime > /tmp/uptime.txt
echo "Hello World" > /tmp/hello.txt
The playbook runs the tasks sequentially from the first task to the last.
Run Commands With Pipes and Redirection
As previously mentioned, the shell module also accepts pipes and redirections. In fact, the previous playbook leveraged the redirection symbol ( >
) to save the output of the listed commands to separate files.
Suppose you want to list all the text files created in the /tmp directory and save the result to another file called dirlist.txt in the same directory.
Here is what the shell command would look like.
ls -l /tmp | grep .txt > /tmp/dirlist.txt
The first part of the command lists all the files in the /tmp directory. The result is then piped to the grep command which filters the output to include only the text files. The final output is then saved to the dirlist.txt file using the ‘greater than’ redirection symbol.
Now let us go a step further and print the result to stdout. For that, a second task is required. The goal is to display the contents of the dirlist.txt file to stdout. The shell command for listing the file’s content is:
cat /tmp/dirlist.txt
The output of the command is then registered in a variable called displayfile and the message is displayed to stdout using the debug module. Here is what the entire playbook looks like.
---
- name: Shell module example
hosts: webservers
tasks:
- name: List text files in tmp directory and save result in output file
shell: "ls -l /tmp | grep .txt > /tmp/dirlist.txt
- name: Display the contents of the output file
shell: cat /tmp/dirlist.txt
register: displayfile
- debug:
msg: "{{ displayfile.stdout_lines }}"
When the playbook is executed, all the text files in the /tmp directory, including the dirlist.txt file, are printed to stdout:
Discover how Tempesta, an open-source application delivery controller (ADC), leveraged Cherry Servers' bare metal cloud to complete tests and validation of their ADC successfully, benefiting from 99.97% uptime, server customization, and 24/7 technical support.
Prevent Shell Injection
Since it runs commands on the shell on remote targets, the shell module is susceptible to shell command injections. Shell injection is an attack vector in which the attacker runs arbitrary commands on the host to compromise the underlying infrastructure.
When using Shell Module variables, Ansible recommends to use the quote
filter to thwart shell injection threats.
{{ variable_name | quote }}
Consider a simple playbook below.
---
- name: Ansible quote filter demo
hosts: webservers
vars:
- username: cherry
tasks:
- name: Print variable
debug:
msg: " Running playbook as user {{ username | quote }}"
The username
variable is referenced at the very end by the msg
parameter using the quote
filter to prevent an arbitrary command string from being executed if it would be injected with the username variable.
Also read: How to use 'when' condition in Ansible
Conclusion
The Ansible shell module is a useful module that can help you quickly execute simple tasks on managed hosts. A perfect substitute for the shell module is the command
module. It provides a more reliable and secure way of executing tasks as commands are not processed on the shell of remote targets. However, if you insist on using the shell module, don’t forget to include the quote
filter when using templated variables in your playbooks to prevent shell injection attacks.