Ansible shell
module is designed to execute Shell commands against the target Unix based hosts. Unlike the Ansible command module, Ansible Shell would accept any highly complexed commands with pipes, redirection etc and you can also execute Shell scripts using Ansible Shell module.
The Advantage of Ansible Shell module of supporting highly complexed commands with pipes
and semicolons
can also be a disadvantage from the security perspective as a single mistake could cost a lot and break the system integrity. But there are some security best practices you can follow while using Shell module and you will be fine. It will also be discussed in this post.
Before we proceed further to see the examples. I must mention something important.
Ansible Shell module is designed to work only with Linux based Machines and not Windows. For windows you should use
win_shell
moduleThough Ansible
Shell
module can be used to execute Shell scripts. Ansible has a dedicated module namedScript
which can be used to copy the Shell script from the control machine to the remote server and to execute.Based on your requirement you can use either Script or Shell module to execute your scripts.
Let us see the syntax of how to use ansible shell module in Adhoc and playbooks.
Index
- Quick Syntax of Ansible Shell in ADHOC
- Quick Syntax of Ansible Shell in Playbook
- Ansible Shell module Examples
- Ansible Shell module to execute a Single Command
- Ansible Shell - Execute a Command with Pipe and Redirection
- Execute a Shell Script with Ansible Shell command
- Run a Shell command in a specific directory
- Run a Shell Command only if the file present or not present
- Using Templated Variable and Usage of Quote Filter - Security Best practice
- Execute Multiple commands in a Single Shell
- Conclusion
A Quick Syntax of Ansible Shell module - ADHOC
Here is the quick Syntax of Ansible Shell module in ADHOC manner.
To know more about Ad hoc commands and how to use them please refer to the following Cheat Sheet.
Ansible AD HOC commands Cheatsheet
A Quick Syntax of Ansible Shell module in a Playbook
The Beauty of Playbook is the way it looks and written. Since it is written in yaml
it can be easily understood as well.
The following picture would demonstrate how an ADHOC command would be transformed as a PLAY of a Ansible Playbook.
If you are new to Playbook. I recommend you reading this article before proceeding further
Ansible Playbook Introduction and Example
Ansible Shell Examples
- Ansible Shell module to execute a Single Command
- Ansible Shell - Execute a Command with Pipe and Redirection
- Execute a Shell Script with Ansible Shell command
- Run a Shell command in a specific directory
- Run a Shell Command only if the file present or not present
- Using Templated Variable and Usage of Quote Filter - Security Best practice
- Execute Multiple commands in a Single Shell
Before we proceed. I presume that you have basic knowledge about Ansible and know what is hostgroup, Play etc. If not. please read the Ansible Playbook Introduction and come back here
Example 1: Execute a Single Command with Ansible Shell
Our First Example is to run any command on the remote host using the Shell module.
Rather taking any command, Let us follow the same convention we have followed in this post and get the date of the remote server. In my case the remote server is under the hostgroup named testservers
---
- name: Shell Examples
hosts: testservers
tasks:
- name: Check Date with Shell command
shell:
"date"
register: datecmd
tags: datecmd
- debug: msg="{{datecmd.stdout}}"
In the preceding example, you can see that we are running our playbook against a hostgroup named testservers
and executing a simple date
command and saving the output of that command into a Register variable named datecmd
At the last line, we are retrieving the registered variable and printing only the date command output stored in the stdout
property of datacmd
Here is the execution output of the same playbook as recorded
Hope this gives you an idea about the playbook and how Shell module works. Let's explore more.
Example 2: Execute a Command with Pipe and Redirection
As said earlier Shell module would accept the Linux commands with symbols such as Pipes, Redirections and Semicolons.
Now let us take some Linux command with Redirection and Pipes.
Let us suppose that we want to execute a given below command with Pipe and redirection. It is a basic ls
command with some processing with awk
and we remove the empty lines with sed
and redirect the final output to a file named dirlist.txt
ls -lrt|awk '{print $9}'|sed '/^$/d' > /tmp/dirlist.txt
Here is the playbook with the preceding command.
---
- name: Shell Examples
hosts: testservers
tasks:
- name: Dir list and write to file
shell:
" ls -lrt /apps|awk '{print $9}'|sed '/^$/d' > /tmp/dirlist.txt "
register: lsout
tags: lsout
- name: Display the file
shell: cat /tmp/dirlist.txt
register: displaylist
- debug: msg="{{displaylist.stdout_lines}}"
In the preceding playbook, we are executing the command without any modification and on the next play Display the file
we are trying displaying the file content with cat
and saving the output to a register variable named displaylist
At last, we are using debug to display the output saved in the displaylist
variable
Here is the execution output of the playbook.
Example 3: Execute a Shell Script with Shell command
Though it is recommended to use script
module for executing the shell script. Shell
module can also execute the scripts just the way you would execute it on your terminal.
Here is the playbook which starts the tomcat server on the remote server by executing the tomcat's startup script.
---
- name: Shell Examples
hosts: testservers
tasks:
- name: Start tomcat
become: yes
become_user: tomcat
async: 10
poll: 0
shell:
"./startup.sh"
args:
chdir: "/apps/tomcat/tomcat8/bin"
register: datecmd
tags: datecmd
- name: Validate if tomcat is UP
tags: tomvalidate
wait_for:
host: "localhost"
port: 8080
delay: 10
timeout: 30
state: started
msg: "Tomcat server is not running"
There are playbooks in the playbook, The First one is to start the tomcat using the shell
module and the another is to validate if the service is running by validating the port status
using wait_for
module.
Some Important Note *:
You might wonder why I have used the
async
and thepoll
directives here along with the shell module.The reason is that my Tomcat JVM is a long running process, If I try to start my Tomcat without this asynchrnous flag. My Tomcat process would be stopped as soon as my play is completed.
If you want your command or the script you are using in Shell to start something which should be UP and RUNNING even after your playbook completed. You should use async flag
In other words this is called `Fire and Forget` in Ansible parlance. I will write a post about this shortly.
Here is the execution output of the preceding Playbook.
Example 4: Run a Shell Command in a Specific directory
Sometimes we want to execute a command after cd
ing into some specific directory. For example, you can consider the previous example of starting tomcat. If you have noticed, we used a parameter named chdir
to specify in which directory the command should be executed.
So this example is about the same chdir
parameter.
Since this parameter is self-descriptive. I hope you can easily understand it without further ado.
In the previous example we have already used this chdir
.However, I will give one more example down below
---
- name: Shell Examples
hosts: testservers
tasks:
- name: Open /etc/password file
become: yes
async: 10
poll: 0
shell:
"cat password"
args:
chdir: "/etc"
register: fileout
tags: fileout
- debug: msg="{{ fileout.stdout-lines }}"
We all know that the Linux passwd file is available in the /etc
directory. I am trying to display the content of this file, In order to explain this chdir, I have made my shell command with just a file name and not the path.
I am passing the directory name with the help of chdir
so the this play would successfully display the content of etc/passwd
Example 5: Run a Shell Command based on file availability
We cannot always go blind and try to execute a command/script without any prior validation. In most of the cases, it might be necessary that we should check if the execution is really needed?
There are various conditional based execution methods available in Ansible. Here we are going to see a quick one, almost built-in with many modules.
The creates
parameter.
All it does is to tell a Shell that the command we are executing would create some file. As a biproduct it also validates if the file is already available and skips the command execution if the file is already present. Thereby avoiding a Duplicate process Creation and protect the system integrity by an accidental overdo.
Here is a simple example with creates
in it.
---
- name: Shell Examples
hosts: testservers
tasks:
- name: Start the DB batch upload
become: yes
async: 10
poll: 0
shell:
"./startbatchupload.sh"
args:
chdir: "/apps/dbscripts/"
creates: "startbatchupload.lock"
register: fileout
tags: fileout
What we do in the previous example is that we try to start an important db patch process after validating the presence of a lock file startbatchupload.lock
The lock file is supposed to be created by the script that we are trying to execute. If it is already present which means there is another instance of the same script already running on the server. Thats the logic behind this validation.
Example 6: Usage of quote filter to avoid Injection
In all these previous examples, we were executing a Shell commands directly specified in the Shell
module. We have not used any variables and templating like declaring the command to be executed as a varible and calling it out later in the play.
I presume you are aware of the Jinja2 templates
in Ansible. It is the way we should access the variables inside the playbook. ( more like a $VARIABLE_NAME in shell)
First, let us see how to declare the command as variable and use it later.
For better understanding, Let me take the same playbook we have used in Example 5 and make some modification.
---
- name: Shell Examples
hosts: testservers
vars:
- script-to-start: "./startbatchupload.sh"
tasks:
- name: Start the DB batch upload
become: yes
async: 10
poll: 0
shell:
"{{ script-to-start }}"
args:
chdir: "/apps/dbscripts/"
creates: "startbatchupload.lock"
register: fileout
tags: fileout
In the preceding playbook you can see that we have declared the script name as a variable and calling it out later inside the play using Jinja2 filter "{{ script-to-start }}"
Thought it would look Good and work good. It has a security issue. As it is prone to Injection attack
it is more like an `SQL injection` where the hacker can change the command at the runtime.
In order to avoid the same. Ansible recommends using quote filter whenever you have to template the variable in the Shell module like given below.
WRONG WAY : "{{ script-to-start }}"
THE RIGHT WAY : "{{ script-to-start | quote }}"
as shown in the above snippet, the right way to template the Shell Command is to use a quote
at the end.
the modified playbook would like this
---
- name: Shell Examples
hosts: testservers
vars:
- script-to-start: "./startbatchupload.sh"
tasks:
- name: Start the DB batch upload
become: yes
async: 10
poll: 0
shell:
"{{ script-to-start | quote}}"
args:
chdir: "/apps/dbscripts/"
creates: "startbatchupload.lock"
register: fileout
tags: fileout
Example 7: Execute multiple commands in a Single Shell - Play
In all the previous examples we have only seen a Single command in a Shell module, Last but not least you should also know that Shell can accept multiple commands ( a batch) together in a Single Shell Play.
In fact, You can write your own Shell script alike inline with Ansible Shell module.
Here is the playbook, where I have grouped some shell commands to perform a Controlled and Clean tomcat restart.
The playbook is designed to perform the following steps in an order.
- Stop the TomcatServer
- Clear the Cache
- Truncate the Log file
- Start the instance
---
- name: Shell Examples
hosts: testservers
tasks:
- name: Clear Cache and Restart tomcat
become: yes
delay: 10
async: 10
poll: 60
shell: |
echo -e "\n Change directory to the Tomcat"
cd tomcat8/
echo -e "\n Present working directory is" `pwd`
echo -e "\n Stopping the tomcat instance"
bin/shutdown.sh
echo -e "\n Clearning the tmp and work directory of tomcat"
rm -rfv tmp/*
rm -rfv work/*
echo -e "\nTruncate the log file"
> logs/catalina.out
echo -e "\nDirectory listing"
ls -lrtd logs/catalina.out
echo -e "\nStarting the instance"
bin/startup.sh
args:
chdir: "/apps/tomcat/"
register: fileout
tags: fileout
- debug: msg="{{ fileout.stdout_lines }}"
You might really get a question this time how this would run and what output it would give. No worries, refer the following screen record.
So it works!.
However!. I recommend writing a Script and using the script module instead of writing an inline script like this but let the requirement be the Judge.
Conclusion
Ansible Shell module is powerful and widely used, but with a lot of power comes the great fear. As mentioned earlier, There are chances Ansible Shell module could lead to an Injection attack or break the system integrity so remember to use quote
with Template variables and follow other best practices.
For controlled execution of Shell commands, You can use the Ansible Command
module but as I already mentioned, Let your requirement be the judge.
Hope it helps!.
Rate this article [ratings]
Cheers
Sarav AK
Follow me on Linkedin My Profile Follow DevopsJunction onFacebook orTwitter For more practical videos and tutorials. Subscribe to our channel
Signup for Exclusive "Subscriber-only" Content