Introduction to Shell and Command Modules in Ansible

The Ansible shell and command modules are used for executing commands in remote servers. Both modules take the command name followed by a list of arguments.

Ansible Shell Module and Command Module

We can use the shell module when we need to execute a command in remote servers, in the shell of our choice. By default, the commands are run on the /bin/sh shell. You can make use of the various operations like ‘|’,'<‘,’>’ etc. and environment variables like $HOME.

If you don’t need those, you should use the command module instead. The command module does not process the commands through a shell. So it doesn’t support the above-mentioned operations.

You give the command you want to execute the same way you give it on a Unix shell, command name followed by arguments.

- name: Executing a Command Using Shell Module 
  shell: ls -lrt > temp.txt

- name: Executing a command using command module
  command: cat hello.txt

The first command lists all the files in the current folder and writes that to the file, temp.txt. The second example displays the content of the hello.txt file.

Changing Default Directory and Shell

In the last examples, the command will always be executed in the default directory. You can change this behavior, and specify the directory path where you want to run the command using chdir parameter. This is available for both shell and command modules.

You can also change the default shell by specifying the absolute path of the require shell in the executable parameter.

- hosts: loc
  tasks:
  - name: Ansible Shell chdir and executable parameters 
    shell: ls -lrt > temp.txt 
    args:
      chdir: /root/ansible/shell_chdir_example 
      executable: /bin/bash
- hosts: loc
  tasks:
  - name: Ansible command module with chdir and executable parameters
    command: ls -lrt
    args:
      chdir: /home/mdtutorials2/command_chdir_example
      executable: /bin/bash

In both examples, I am using the ‘Bourne Again SHell’ by giving the absolute path /bin/bash. And I have changed the directory to /root/ansible/shell_chdir_example.

Note: Shell and command modules are among the few modules in Ansible which are not idempotent by default. So if you rerun a task, the system status is not checked before running. For some tasks like the last example, it is also desired.

Making tasks idempotent by using creates keyword

You can make the shell and command modules idempotent using the ‘creates‘ keyword and ‘removes‘ keyword. This depends on the presence or absence of a file. i.e., we can specify the task should run only if a file exists or removed.

In the below example, we are creating a new file. If the file is already present, we do not need to run the command again. So we can set the filename in the ‘creates’ parameter.

- name: Ansible shell module creates parameter example.
  command: touch shell.txt
  args:
    chdir: /root/ansible
    creates: shell.txt

You can see the difference in the below GIF. The playbook shell_no_creates.yaml do not have the ‘creates’ parameter and the ‘shell_creates.yaml’ has it.

Ansible shell and command modules with 'creates' parameter

You can use the ‘removes‘ parameter in a similar manner. Here I am checking if the file already exists before trying to remove it. Else it would throw an error if the file doesn’t exist.

- name: Ansible command module creates parameter example.
  command: rm shell.txt
  args:
    chdir: /root/ansible
    removes: shell.txt

Executing Multiple Commands

If you have to execute multiple commands, you can give them in both shell and command modules using ‘with_items’.

In the following example, I am creating three files; hello.txt, hello2.txt, and hello3.txt. Since I have given the {{ item }} keyword in the command, it will be replaced with an element of the list in each iteration. Make sure the level of indentation of with_items is on the same level as the module name. For more information on looping with with_items, refer Ansible docs.

- hosts: loc 
  tasks: 
  - name: Ansible command module multiple commands 
    command: "touch {{ item }}"
    with_items: 
      - hello.txt 
      - hello2.txt 
      - hello3.txt 
    args: chdir: /root/ansible
- hosts: loc
  tasks:
  - name: Ansible shell module multiple commands
    shell: "cat {{ item }} | grep ansible"
    with_items:
      - hello.txt
      - hello2.txt
      - hello3.txt
    args:
      chdir: /root/ansible

Ansible shell module environment variables

You can also access the LINUX environment variables in Ansible.

The following task will print the name of the current user using USER variable.

- hosts: loc
  tasks:
  - name: Ansible shell module environment variables
    shell: echo $USER
    register: command_result

  - debug: msg={{ command_result.stdout }}

You can also set new environment variables while executing the task using the ‘environment’ parameter.

- hosts: loc
  tasks:
  - name: Ansible shell module setting environment variable
    shell: echo $NEW_ENV_VAR
    register: command_result
    environment:
      NEW_ENV_VAR: "variable_set"

  - debug: msg="{{ command_result.stdout }}"

The above task will set the NEW_ENV_VAR environment variable.

Note 1: This variable is set only for that task. In the next task, since you are accessing a new shell, the NEW_ENV_VAR variable will not be available.

Note2: You can also set multiple environment variables.

    environment:
      NEW_ENV_VAR: "variable_set"
      NEW_ENV_VAR2: "variable_set2"

Ansible shell module output

When you execute a command using the shell module, it also returns some fields, which you can use to debug the tasks. You can see the command executed, the output, the errors etc. The full list of return values for this module is available at Ansible docs. You can store the result of the command in a ‘register’.  And you can either see the full list of return values or specific ones.

In the following example, the output of the echo command will be stored in a register named ‘command_result’. It will contain all the return values of the shell module. So if you print the value, then it will contain the ‘command you executed’, the ‘output shown in the remote system’, ‘time when the command was executed’ etc. If you don’t want the full list and only the output or command,  then you can do it by specifying it like command_result.stdout or command_result.cmd.

- hosts: loc
  tasks:
  - name: Ansible shell module output
    shell: echo $PWD
    register: command_result

  - name: Ansible shell register result
    debug: msg="{{ command_result }}"

  - name: Return only the shell standard output
    debug: msg="{{ command_result.stdout }}"
The first debug task will show the output similar to below.
ok: [ansible-control-machine] => {
    "msg": {
    "changed": true,
    "cmd": "echo $PWD",
    "delta": "0:00:00.003216",
    "end": "2017-03-26 07:45:25.055561",
    "rc": 0,
    "start": "2017-03-26 07:45:25.052345",
    "stderr": "",
    "stdout": "/home/mdtutorials2",
    "stdout_lines": [
    "/home/mdtutorials2"
   ],
   "warnings": []
   }
   }

The second debug task will show just the stdout value.

ok: [ansible-control-machine] => {
    "msg": "/home/mdtutorials2"
}

Controlling errors with failed_when

When you are running the shell module or the command module, you might want it to fail only if particular conditions are met and not the default errors. Ansible provides a ‘failed_when’ parameter for this. This is a common parameter and not part of the shell module or command module.

In the following Ansible task, I am trying to remove the shell.txt file. When you repeat the task, the task will fail in the normal case. But using the failed_when parameter, I changed the fail condition as to only fail when the string ‘Permission denied’ is in the stderr. I had already captured the output of the command in a variable(register) called output.

- name: Executing a command using command module
    command: rm shell.txt
    args:
      chdir: /root/ansible
    register: output
    failed_when: "'Permision denied' in output.stderr"

The shell module provides some return values like ‘stderr’ and ‘stdout’.  You can find more return values in the Ansible documentation.

Ansible shell modules with failed_when

For more details on both modules refer shell module and command module.

 Ansible Adhoc shell command

You can also give the shell command using the Adhoc method.

ansible all -m shell -a "ls -lrt" 

The above command will execute the list command on the remote server. While using the Adhoc method, the default module is shell. so you can also give the command as

ansible all -a "touch hello_world.txt"