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