When you start to familiarize yourself with Ansible, especially in the early phase, you’ll often call Linux commands with command or shell—and later, you’ll realize that using a specific module would have been better.
You could have used the file module instead of a mkdir call, the user module instead of a useradd call, the synchronize module instead of a rsync call.
But first, you would have to know all of this, and second, there isn’t a module for everything. So now, we should take a look at how command and shell calls behave in the playbook context. Let’s start with a simple example:
---
- hosts: debian
gather_facts: no
tasks:
- name: Determine space requirements
command: df -h /
$ ansible-playbook command1.yml
PLAY [debian] ***************************************************************
TASK [Determine space requirements] *****************************************
changed: [debian]
PLAY RECAP ******************************************************************
debian : ok=1 changed=1 unreachable=0 failed=0
You’ll notice immediately that, in contrast to an ad hoc call, you see nothing. Upon closer inspection, you’ll also see that Ansible reports a change (even though we are quite sure that a df -h should not cause any changes to the target system). The latter is easy to explain: Ansible does not perform a deep analysis of the Linux commands that are about to be executed, and therefore, it simply does not know the effect here. By default, Ansible always assumes a change for such a task—but you can define this assessment yourself with the task changed_when attribute. Once you’ve finished learning about conditions in Ansible, you’ll have many options available. However, in this case, simple negation using changed_when: false is already sufficient. A df command never causes a change to the system:
---
- hosts: debian
gather_facts: no
tasks:
- name: Determine space requirements
command: df -h /
changed_when: false
However, the issue remains that you do not see the output of the command.
First of all, you should come to terms with the fact that playbooks are not meant to produce pretty output. Rather, they are intended to perform actions on target systems to achieve a desired target state, and pretty output is irrelevant.
Therefore, all standard outputs from command and shell calls are suppressed. However, we can access them (and many other pieces of information) by taking a small detour: Ansible can store all information about the execution of a task in a structured variable (the register keyword). You can then access that variable in later tasks, as follows:
---
- hosts: debian
gather_facts: no
tasks:
- name: Determine space requirements
command: df -h /
changed_when: false
register: df_cmd
- debug:
msg: ''
$ ansible-playbook command3.yml
PLAY [debian] ***************************************************************
TASK [Determine space requirements] *****************************************
ok: [debian]
TASK [debug] ****************************************************************
ok: [debian] => {
"msg": [
"Filesystem Size Used Avail Use% Mounted on”:
"/dev/mapper/debian--12--vg-root 62G 1.3G 57G 3% /"
]
}
PLAY RECAP ******************************************************************
debian : ok=2 changed=0 unreachable=0 failed=0
First, I would like to clarify the technical classification of registered variables:.
Important: Registered variables are technically the same as set_fact variables. They are host specific, meaning they are only visible to the host that has just executed the task. They also have exactly the same precedence.
A registered variable has various fields or attributes that depend on the type of task. Here, we accessed the stdout_lines field. It contains a list of the individual output lines, so it is close what would have been printed. (You can somewhat influence the display of such structures on the screen.)
This table shows all the fields that would appear if you were to output the complete df_ cmd variable in the debug task:
| Attributes | Values |
| changed | Did the task make a change to the system? |
|
failed |
Did the task fail? |
| cmd | This is the command with parameters. |
| rc | This is the UNIX return code of the command |
| start | This is the start time. |
| end | This is the end time. |
| delta | This is the execution duration. |
| stdout | This is the joined standard output with newliness as \n. |
| stdout_lines | This is the list of individual standard output lines. |
| stderrr | This is the standard error output that's similar to stdout. |
| stderr_lines | This is the standard error output that's similar to stdout_lines. |
Tip: The debug module offers an even simpler way to output the contents of a variable. Instead of using
- debug: msg=''
you can simply use
- debug: var=some_variable.
Editor’s note: This post has been adapted from a section of the book Ansible: The Practical Guide for Administrators and DevOps Teams by Axel Miesen. Axel is an Ansible coach. His interest in Linux systems began with his studies at the University of Kaiserslautern, where he studied mathematics and computer science. After graduating in 1998, he began working as a consultant and trainer and has passed on his Linux knowledge and experience to numerous professionals.
This post was originally published 12/2025.