As an Ansible user, you‘ll inevitably need to execute commands and scripts on your managed nodes. Ansible provides two common modules for basic command execution – the shell and command modules. At first glance, both seem interchangeable. However, important differences in how shell and command run commands impact security, flexibility, and performance.
Understanding when to use each module is key to crafting reliable, secure Ansible automation. In this comprehensive guide, we’ll cover:
- Ansible module fundamentals
- How
shellandcommanddiffer - When to use
shellovercommand(and vice versa) - Best practices for command execution in Ansible
By the end, you’ll have the knowledge to confidently harness the power of Ansible‘s command modules for your automation needs. Let‘s get started!
Why Ansible Modules?
Before diving into specifics on shell and command, it helps to understand why Ansible encourages using modules for automation tasks.
Ansible modules are reusable units of automation logic abstracted into a common interface. Ansible ships with over 1800 built-in modules covering practically every administrative task like package management, cloud provisioning, database management, and more.
Instead of executing raw shell commands, invoking Ansible modules provides advantages like:
Idempotence – Modules are designed to be idempotent, meaning running a module multiple times brings the system to the desired state. For example, using a file module to create a file will do nothing if the file already exists.
Isolation – Modules execute in isolation, avoiding side effects from the user’s shell environment or sysadmin’s personal configs. This makes Ansible automation reliable across different systems.
Abstraction – By abstracting complexity into modules, Ansible provides a consistent experience across disparate operating systems and environments. You don‘t have to write special cases for Debian vs RHEL package managers.
Error handling – Modules gracefully handle errors and provide detailed exception messages and return codes. Playbooks can take action based on module failure.
In short, modules promote reusable, robust automation over fragile shell scripts. Whenever possible, use a module over an ad-hoc shell command or script.
That said, Ansible still provides the shell and command modules for directly executing shell commands when needed. Understanding how these modules differ is key to using them effectively.
Shell Module: Flexible but Potentially Risky
The shell module runs the given command through the target system‘s shell, by default /bin/sh. For example:
- name: Run a script
shell: /opt/scripts/setup.sh
This allows using the full power of the shell:
Pipes and Redirections
- shell: |
echo "Starting deploy"
cd /app
./deploy.sh
echo "Deploy finished" > /opt/logs/deploy.log
Environment Variables
- shell: echo "Home directory is $HOME"
Changing User Context
- shell: |
sudo su -
cd /root
ls
Scripts or One-liners
The shell module can run inline commands or execute scripts interchangeably:
- shell: ./setup.sh
- shell: |
apt update
apt install nginx -y
This flexibility makes shell usage appealing. But with great power comes great responsibility!
Security Risks
Passing uncontrolled user input to the shell risks injection vulnerabilities. Ansible mitigates this by quoting arguments, but cautions:
"It is usually safer to use the ‘command’ module when possible, as it bypasses the shell‘s string interpolation and startup scripts. Always sanitize any user input passed to the ‘shell’ module."
Non-Idempotence
The shell module runs arbitrarily complex commands, making idempotence enforcement difficult. A subsequent run may produce different results or errors.
For these reasons, Ansible recommends the command module for most use cases.
Command Module: Direct and Idempotent
The command module bypasses the shell and executes the given command directly. For example:
- name: Run setup script
command: /opt/scripts/setup.sh
This provides several advantages:
Security – Avoiding the shell bypasses many risks like code injection. The command module provides no facilities for redirection, pipes, or environment variable expansion.
Idempotence – Executing a fixed command promotes idempotent behavior, since the same command produces the same result if run repeatedly.
Performance – Without the overhead of invoking a shell, the command module has less performance overhead.
Isolation – The command executes with no dependencies on the user‘s shell environment.
The tradeoff is losing access to shell features like we saw with shell:
No Pipes or Redirections
# Fails with command module:
- command: ls /home > /opt/output.txt
No Environment Variables
- command: echo $HOME
# $HOME is undefined
No User Context Changes
Commands execute in Ansible‘s context. Privilege escalation requires Ansible‘s become keyword.
Only Handles Simple Commands
The command module executes a single command string. For conditional logic, loops, and so on, use Ansible playbook constructs or write a custom module.
Given these limitations, command excels at safely executing simple static commands. Avoid command when you need shell features.
Shell vs Command Examples
Let‘s look at some examples to cement when to use shell over command and vice versa.
Shell
Use shell when you need to:
- Pipe command output
- shell: |
ls -l /etc | grep sys
- Redirect output
- shell: python3 app.py > /var/log/app.log
- Expand environment variables
- shell: echo $HOME
- Change user context
- shell: |
sudo su -
cd /root
Command
Use command when you want:
- Simple, direct execution
- command: /usr/bin/python3 /app/script.py
- Isolation from the environment
- command: /opt/script
# Runs consistently regardless of $PATH
- Idempotent behavior
- command: touch /tmp/placeholder
# Creates file only if it doesn‘t exist
- Avoid the shell for security
- command: rm *
# Avoids risks like rm -rf *
Recommended Practices
Based on Ansible‘s guidelines and the experience of Ansible engineers, here are some best practices to follow:
When possible, use an Ansible module over shell or command. 300+ modules ship with Ansible covering most administrative tasks. Modules provide error handling, idempotence, and OS abstraction.
Use command when you simply need to execute a static command. Prefer command over shell for safety and idempotence. Avoid command when you need shell features.
Only use shell when shell features are required. If you need pipes, redirections, environment variables, or scripts, use shell selectively. Understand the risks associated with shell injection and non-idempotent operations.
Always quote command arguments. This applies to both modules. Quote arguments to avoid injection risks and inadvertent splitting/interpolation.
Use args: rather than shell: or command: when possible. Passing structured data to a module using args: is safer than injecting a string.
Combine both modules to balance flexibility and safety. For example, run setup commands with command then selectively enable shell where needed.
Following these guidelines will help you avoid pitfalls and craft robust Ansible automation using shell and command.
Key Takeaways
-
The
shellmodule runs commands through the shell (/bin/sh) providing access to pipes, redirections, variables, and scripts. -
The
commandmodule bypasses the shell, sacrificing flexibility for safety and idempotence. -
Prefer
commandfor simple static commands to avoid shell security risks. -
Use
shellselectively when shell features like pipes, variables, and user context changes are required. -
Ansible modules provide abstraction, error handling and idempotence over raw
shellandcommand. -
Quote arguments, use
args:, and follow best practices to avoid issues with both modules.
The shell and command modules form the backbone of command execution in Ansible. Understanding their capabilities and differences unlocks the full potential of Ansible automation. With the guidelines presented here, you can confidently harness both modules for your automation needs.


