Ad-Hoc Commands: Ansible Without a Playbook
Scenario: At 02:10 someone needs to know the uptime and free disk of forty machines, right now. Writing a playbook first would be absurd.
New words, in plain English
- Ad-hoc command - a single module run against a pattern of hosts straight from the command line, with no file involved.
-m- which module to run.-a- the arguments to give it.pingmodule - not ICMP ping. It verifies that Ansible can connect AND run Python on the target.
The ansible command (as opposed to ansible-playbook) runs one module against a host pattern immediately. It is the right tool for investigation and for genuinely one-off actions, and the wrong tool for anything you will ever want to repeat, review or explain.
The canonical first command is ansible all -m ping. It is worth understanding what a successful pong actually proves: the host resolved, SSH connected, authentication succeeded, a Python interpreter was found, a module was copied, executed and cleaned up. That is nearly the entire Ansible pipeline verified in one line - which is why it is the first thing to run when anything is behaving strangely.
Ad-hoc commands accept the same connection flags as playbooks: -i for inventory, -u for the user, --limit, -o for one-line output.
Analogy: Ad-hoc commands are a phone call; playbooks are a signed contract. The phone call is faster and perfectly appropriate for 'are you there?'. You would not run a production release over one.
A worked example
# Is everything reachable and runnable?
ansible all -m ping
# Gather one fact from a group, one line per host
ansible webservers -m setup -a 'filter=ansible_distribution' -o
# Run a command (note: no shell features - no pipes, no redirects)
ansible production -m command -a 'uptime' -o
# When you genuinely need shell features, use the shell module
ansible production -m shell -a 'df -h / | tail -1' -o
The distinction between command and shell shows up here for the first time and never goes away. command executes a binary directly with arguments - no shell involved, so no pipes, no redirection, no globbing, no environment-variable expansion, and importantly no shell-injection surface. shell runs the string through /bin/sh, which gives you all of those features and all of their risks.
Prefer command unless you specifically need a shell feature. And prefer a real module over either: ansible.builtin.file over command: mkdir, ansible.builtin.copy over shell: echo >>. Modules check before they act; raw commands do not.
Tip: Add -o to ad-hoc commands for one compact line per host. Across forty machines it turns an unreadable wall of JSON into something you can actually scan.
Goal: Put this to work in the ansible-playbook-syntax lab. Open/labs/ansible, pickansible-playbook-syntax, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.