How to Run Remote Commands with Ansible Shell Module

March 16th, 2022
How to Run Remote Commands with Ansible Shell Module

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.

Prerequisites

To follow this guide, you should have:

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'

Ansible shell module check server uptime

To check the memory usage, run:

sudo ansible all -m shell -a 'free -m'

Ansible shell module check memory usage

To check disk space utilization of the host under the db_server group, execute:

sudo ansible db_server -m shell -a 'df -Th'

Ansible shell module check disk usage

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 Ansible playbook

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.

Run Ansible playbook

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 ~"

Check home directory via SSH

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 Ansible playbook

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.

Run Ansible playbook

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 Ansible playbook

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 Ansible playbook

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:

Run Ansible playbook

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.

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.

Helping engineers learn 💡 about new technologies and ingenious IT automation use cases to build better systems 💻

Join Cherry Servers Community

Get monthly practical guides about building more secure, efficient and easier to scale systems on an open cloud ecosystem.

We use cookies to ensure seamless user experience for our website. Required cookies - technical, functional and analytical - are set automatically. Please accept the use of targeted cookies to ensure the best marketing experience for your user journey. You may revoke your consent at any time through our Cookie Policy.