How to Run Remote Commands with the Ansible Shell Module

Shanky

Read more posts by this author.

Ansible is a popular automation platform allowing you to manage thousands of nodes at one time. One of the most useful features of Ansible is its ability to run ad-hoc commands on remote computers with the Ansible shell module.

The shell module allows you to run ad-hoc commands or even small scripts like you’re sitting in front of the local console of each machine. It’s a handy module if you need to quickly run a command on a managed node.

In this tutorial, you’re going to learn what the Ansible shell module is, how it works, and how to use the Ansible shell module to run commands on remote hosts.

Prerequisites

This post will be a step-by-step tutorial on the Ansible shell module. If you’d like to follow along, be sure you have the following in place:

  • An Ansible controller host – This tutorial will be using Ansible v2.9.18 on an Ubuntu 18.04.5 LTS machine. Learn how to set up an Ansible controller host here.
  • A user account on the Ansible controller host that will allow you to create simple playbooks and run them on a managed node.
  • A remote computer to run commands on – This tutorial will be using a node called SRV1 and webserver.

Running Ad-Hoc Commands with the Ansible Shell Module

In its simplest form, the Ansible shell module can run a single command on a remote host. To do this, a one-line command on your Ansible controller will work. For example, let’s run a simple command on a remote host to print the PATH environment variable‘s value on a remote machine.

Log onto your Ansible controller and run the following command. This command uses the shell module (-m) to connect to the webserver machine and pass an argument (-a) which is the command to execute. In this instance, it’s running echo $PATH to return the PATH environment variable’s value.

# webserver is host 
# -m is syntax which is used with any module
ansible webserver -m ansible.builtin.shell -a 'echo $PATH' 
ansible webserver
ansible webserver

Running Commands within a Playbook

Looks can be deceiving when it comes to the Ansible shell module. You can run remote commands with this module in many different ways. But first, let’s ease in slowly and learn how to run a simple command.

Let’s say you need to create a text file on a managed node at /opt/new_file.txt on a Linux host. You’d like to use the Ansible shell module to do that.

1. SSH to your Ansible controller host.

2. Create a directory called ansible_shell_module_demo in your home directory. This directory will contain the [playbook] you’ll use to invoke the shell module.

mkdir ~/ansible_shell_module_demo
 cd ~/ansible_shell_module_demo

3. Open your favorite text editor and create a file called my_playbook.yml in the ~/ansible_shell_module_demo directory and paste in the following YAML playbook contents. This playbook has a single task that uses the Ansible shell module to send some text (I am creating a new file) to the /opt/new_file.txt text file.

Since the playbook will be creating the file in the /opt directory, Ansible will need to run with sudo permissions using the become attribute.

Ansible playbooks are written in YAML. To learn more about YAML, [click here]

tasks:
     - name: Create a text file in opt directory using /bin/sh shell
         ansible.builtin.shell: echo "I am creating a new file" > /opt/new_file.txt
         become: true   # Ensure that the highest permissions are used on the remote host

By default, when you specify shell as the module to use for a task, it will use the Bash shell. To invoke the Ansible shell module, you must use ansible.builtin.shell. If you have Windows-based remote machines, you must use the ansible. windows.win_shell module instead.

4. Create a subfolder called SRV1 under ~/ansible_shell_module_demo. This folder represents the remote node the playbook Ansible will execute on.

Structure of demo folder
Structure of demo folder

5. Now, invoke the playbook to copy it to and execute the task on the remote host.

ansible-playbook my_playbook.yml 

Inventory is basically a collection of remote hosts either by their hostnames or by their IP address. If you wish to read in-depth how to create inventories, click here.

You can see below that the TASK has a status of changed meaning the remote host wasn’t in the proper state and was changed to run the command.

TASK has Changed
TASK has Changed

Using Arguments

In the previous example, you ran the Ansible shell module to invoke a simple command. Sometimes, you need to customize that behavior using arguments. Arguments allow you to pass configuration values to the shell module as it runs.

For example, when you run a shell command, the command also has a working directory or directory the command knows it’s running in. Perhaps, you must ensure you run a command in the /opt directory. To change that behavior, you’d use the chdir argument.

Assuming you’re still in your Ansible controller host:

1. Open the playbook in your favorite text editor you recently created.

2. Replace the playbook text with the below playbook. This playbook uses the args attribute to pass the chdir argument to the task which changes the working directory to /opt before the command executes.

tasks:
     - name: Change the working directory to /opt before executing the command
       ansible.builtin.shell: ls -lh >> my_text_file.txt
       args:
         chdir: /opt                    # Changes to /opt directory

You can see below the output is the same as previously.

Output of ansible shell module command with chdir parameter
Output of ansible shell module command with chdir parameter

3. Now, open the playbook again and paste in the following YAML. The below playbook performs the exact same action as the previous step. But, instead, it specifies the command to run (ls -lh) on its own line via the cmd attribute and removes the need for the args attribute.

tasks:
     - name: Change the working directory to /opt
       ansible.builtin.shell:
         cmd: ls -lh                    # To list files under /opt directory
         chdir: /opt                    # changes to /opt directory

4. By default, the shell module uses the sh shell on remote Linux nodes. But, you can change that using the executable argument. By specifying the executable argument like below, the ls command will now use the Bash shell.

tasks:
     - name: Reads all logs files in /opt directory
       ansible.builtin.shell: ls -lh
           args: 
             executable: /bin/bash # Run ls in the Bash shell

Executing Multiple Commands

So far, you’ve only been running a single command a time. Let’s change that. You can also invoke the Ansible shell module to run many commands at once.

On your Ansible controller host:

1. Open a text editor again and create another playbook called my_playbook2.yml in the ~/ansible_shell_module_demo directory.

2. Copy and paste the following playbook into the my_playbook2.yml file. Notice that the shell module will not create four separate files by running four separate commands in the same playbook.


 tasks:
     - name: Create multiple text file in tmp directory with shell module
         ansible.builtin.shell: |       # Multiple commands in Ansible shell module
             echo "This will go in log file"   > /tmp/log_file.txt
             echo "This will go in memory file"> /tmp/memory_file.txt
             echo "This will go in disk file"  > /tmp/disk_file.txt
             echo "This will go in version file"> /tmp/version_file.txt
         become: true
             args:
                 chdir: /var/log             # Changing the directory

3. Now, execute the playbook on the SRV1 node.

ansible-playbook my_playbook.yml --inventory SRV1

You should see that the TASK has a status of changed.

Executing Playbook in SRV1 node
Executing Playbook in SRV1 node

4. Verify Ansible created the files on the remote host as expected.

Using Multiple commands generated multiple files.
Using Multiple commands generated multiple files.

Generating Debug Output

Sometimes playbooks won’t do what you expect and it’s time to start troubleshooting. When troubleshooting, you’ll sometimes need more granular information as to what’s going on inside of the playbook. In that case, you’ll need to read the debug output.

1. Perhaps the previous playbook was causing problems and you want to investigate what’s going on. In that case, use the debug parameter as shown below.

When Ansible runs this playbook, it will generate the output in JSON format displaying the detailed execution of commands.


 tasks:
     - name: Create multiple text file in tmp directory with shell module
         shell: |                    # Multiple commands in Ansible shell module
             echo "This will go in log file"   > /tmp/log_file.txt
             echo "This will go in memory file"> /tmp/memory_file.txt
             echo "This will go in disk file"  > /tmp/disk_file.txt
             echo "This will go in version file"> /tmp/version_file.txt
         register: shell_output
         become: true
             args:
                 chdir: /var/log             # Changing the directory
     - debug: var=shell_output                 # This will provide the output

2. Run the playbook against the SRV1 node again.

ansible-playbook my_playbook.yml --inventory SRV1

If all goes well, you should see output like below.

shell module output
shell module output

3. Now connect to the remote computer and verify the files were again created.

Multiple files created successfully from multiple commands using Ansible shell command
Multiple files created successfully from multiple commands using Ansible shell command

Conclusion

Ansible shell module is a great way to run your commands on remote hosts. It provides a variety of flavored functionalities and gives you a handy way to remotely execute commands.

Subscribe to Stay in Touch

Never miss out on your favorite ATA posts and our latest announcements!

Looks like you're offline!