Ansible - Working with command output
Table of Contents
You have decided to move forward with using/trying Ansible. You can now connect to a device and get
a green success that you get a hello world like command such as show hostname or
show inventory and get the GREEN success on Ansible. Now what. You may want to see the output of
the command that you sent and got information back. This is your post on getting started.
This is the process that I typically go through when developing a playbook for use. Let’s say this is a playbook that you wish to just get show information out of the device, say investigating if there are any configurations that are applied that would be part of a CVE bug, or just operational status.
During this post I will relate the Ansible data structures/formats to that of Python. So the terms will be dictionary (hashes) and lists (lists).
Playbook Design Process
- Make sure that I can connect to the devices with a simple show command
- Get necessary show output
- Debug the outputs of the show commands
- Set facts or take more action based on other outputs
This can get extremely elaborate. I am going to attempt to keep this about the debug commands along the way.
Lab Setup #
First I will just give a quick diagram of the lab environment. This is simulated with EVE-NG. I will be accessing the devices via a management network to show various things.

I am going to connect to just Cisco IOS and Cisco ASA virtual images for this. That can extend as well to any other platform using the standard Ansible 2.9 network modules.
Playbook Play and Task #
First, the initial connection and a simple show command.
Playbook A #
Task 1 (A1) #
The play in the playbook is going to log in and execute the following two tasks:
- Task 1: Issue command
show run interface loopback 0-> Save to a variable named show_commands - Task 2: Debug the output of the variable show_commands, which is stored for each device that is connected to.
---
# yamllint disable rule:truthy
- name: Test command outputs
connection: network_cli
hosts: cisco_routers
gather_facts: no
become: yes
become_method: enable
tasks:
- name: IOS >> Show commands
ios_command:
commands:
- show run interface loopback 0
register: show_commands
- name: SYS >> DEBUG OUTPUT
debug:
msg: "{{ show_commands }}"Here is the output from connecting to a single device:
| |
As we look at the output, the task itself creates the part "msg": This itself shows the output
dictionary. This has several keys: and values. Breaking down each of the keys and values in the
output:
changed: This is a boolean field where you will see if the variable stored (output of a task) had made a change. Most*_commandoutputs will not make changes to the devices.failed: Did the task have a failed return code or notstdout: The standard output, including escape characters, of the output, this output is a liststdout_lines: This is a more human readable output format, that puts the line breaks in. This output is in the format of a list
These outputs are in the forms of lists, so if we want to get access to the actual string of the
command show run interface loopback 0? We need to access the show_commands['stdout'][0].
This will be shown with the updated playbook (a second debug has been added):
---
# yamllint disable rule:truthy
- name: Test command outputs
connection: network_cli
hosts: cisco_routers
gather_facts: no
become: yes
become_method: enable
tasks:
- name: IOS >> Show commands
ios_command:
commands:
- show run interface loopback 0
register: show_commands
- name: SYS >> DEBUG OUTPUT
debug:
msg: "{{ show_commands }}"
- name: SYS >> DEBUG to get to the actual output
debug:
msg: "{{ show_commands['stdout'][0] }}"This now yields this output:
| |
Why Lists? #
So why are stdout and stdout_lines are in the type of lists? This goes back to the section of
the Ansible module ios_commands where commands is fed a list. This means that you can send
multiple commands in a single task. Updating the playbook to be this:
---
# yamllint disable rule:truthy
- name: Test command outputs
connection: network_cli
hosts: cisco_routers
gather_facts: no
become: yes
become_method: enable
tasks:
- name: IOS >> Show commands
ios_command:
commands:
- show run interface loopback 0
- ping 8.8.8.8 source loopback 0 repeat 20 size 1500
register: show_commands
- name: SYS >> DEBUG OUTPUT
debug:
msg: "{{ show_commands }}"
- name: SYS >> DEBUG to get to the actual output for 1st command run
debug:
msg: "{{ show_commands['stdout'][0] }}"
- name: SYS >> DEBUG to get the ping results
debug:
msg: "{{ show_commands['stdout'][1] }}"Which now yields:
| |
We can now see how you may get at particular command outputs, while running multiple commands during one task on the device. From what I can tell, these commands are run sequentially, and not with separate SSH sessions, as during my testing I only ever saw a single SSH session on the device.
Accessing variables from other tasks #
This is something that I stumbled upon at some point that was helpful in multiple play playbooks.
You have multiple plays in a playbook right? So how do you get at information from a previous task?
You access it via the keyword variable hostvars. I’ve added a second play to the previous
playbook. I also added another DNS provider to test my pings to in order to show this.
---
# yamllint disable rule:truthy
# yamllint disable rule:line-length
- name: Test command outputs
connection: network_cli
hosts: cisco_routers
gather_facts: no
become: yes
become_method: enable
tasks:
- name: IOS >> Show commands
ios_command:
commands:
- show run interface loopback 0
- ping 8.8.8.8 source loopback 0 repeat 20 size 1500
- ping 1.1.1.1 source loopback 0 repeat 20 size 1500
register: show_commands
- name: SYS >> DEBUG OUTPUT
debug:
msg: "{{ show_commands }}"
- name: SYS >> DEBUG to get to the actual output for 1st command run
debug:
msg: "{{ show_commands['stdout'][0] }}"
- name: SYS >> DEBUG to get the ping results
debug:
msg: "{{ show_commands['stdout'][1] }}"
- name: See output from previous play
connection: local
hosts: local
gather_facts: no
tasks:
- name: SYS >> Debug variable from previous task
debug:
msg: "{{ hostvars['rtr01']['show_commands']['stdout'][2] }}"This now has the output of the ping test to 1.1.1.1 in the output.
| |
Looping over the output #
You can also loop over the output of the commands as well. I added in some more lines to the debug
that will show how you can loop over all of the commands you issued. In a future post we will
discuss on how to debug through using with_items.
| |
We want to get to each of the stdout outputs. I’ve added with_items and we have a new variable
of item that we reference in the message. We now get the following output related to that task:
| |
Final Run #
Here is the final playbook
---
# yamllint disable rule:truthy
# yamllint disable rule:line-length
- name: Test command outputs
connection: network_cli
hosts: cisco_routers
gather_facts: no
become: yes
become_method: enable
tasks:
- name: IOS >> Show commands
ios_command:
commands:
- show run interface loopback 0
- ping 8.8.8.8 source loopback 0 repeat 20 size 1500
- ping 1.1.1.1 source loopback 0 repeat 20 size 1500
register: show_commands
- name: SYS >> DEBUG OUTPUT
debug:
msg: "{{ show_commands }}"
- name: SYS >> DEBUG to get to the actual output for 1st command run
debug:
msg: "{{ show_commands['stdout'][0] }}"
- name: SYS >> DEBUG to get the ping results
debug:
msg: "{{ show_commands['stdout'][1] }}"
- name: SYS >> DEBUG to see loop
debug:
msg: "{{ item }}"
with_items: "{{ show_commands['stdout'] }}"
- name: See output from previous play
connection: local
hosts: local
gather_facts: no
tasks:
- name: SYS >> Debug variable from previous task
debug:
msg: "{{ hostvars['rtr01']['show_commands']['stdout'][2] }}"Final Run Output
| |
Hope that this has been helpful!