AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible shell Module: Run Commands with Pipes & Redirects (Complete Guide)

By Luca Berton · Published 2024-01-01 · Category: installation

How to run shell commands in Ansible with the shell module (ansible.builtin.shell). Use pipes, redirects, environment variables, chained commands.

Ansible Shell Module: Run Commands with Pipes & Redirects (Complete Guide)

The Ansible shell module (ansible.builtin.shell) executes commands through the shell (/bin/sh) on remote hosts, enabling pipes, redirects, environment variables, and all shell features. This guide covers when and how to use it effectively.

See also: Ansible shell Module: Run Shell Commands with Pipes & Redirects (Guide)

Shell vs Command Module: Key Difference

| Feature | shell | command | |---------|-------|---------| | Pipes (|) | ✅ Yes | ❌ No | | Redirects (>, >>) | ✅ Yes | ❌ No | | Environment variables ($HOME) | ✅ Yes | ❌ No | | Wildcards (.log) | ✅ Yes | ❌ No | | Chaining (&&, ||) | ✅ Yes | ❌ No | | Safer (no injection risk) | ❌ No | ✅ Yes |

Rule of thumb: Use command by default. Use shell only when you need shell features.

Basic Usage

---
- name: Shell module examples
  hosts: all
  tasks:
    - name: Run a simple command
      ansible.builtin.shell: echo "Hello from $(hostname)"
      register: result

- name: Display output ansible.builtin.debug: var: result.stdout

See also: Ansible win_shell Module: Run PowerShell Commands on Windows (Guide)

Pipes and Redirects

- name: Count running processes
  ansible.builtin.shell: ps aux | wc -l
  register: process_count
  changed_when: false

- name: Find large files ansible.builtin.shell: find /var/log -type f -size +100M | sort -rn register: large_files changed_when: false

- name: Write output to file ansible.builtin.shell: df -h > /tmp/disk_report.txt

- name: Append to log file ansible.builtin.shell: echo "Backup completed at $(date)" >> /var/log/backup.log

Multi-Line Commands

Use YAML literal block scalar (|) for complex commands:

- name: Complex multi-line shell command
  ansible.builtin.shell: |
    cd /opt/app
    git pull origin main
    npm install --production
    npm run build
    systemctl restart myapp
  args:
    chdir: /opt/app

See also: Three options to Safely Limit Ansible Playbooks Execution to a Single Machine

Environment Variables

- name: Use environment variables
  ansible.builtin.shell: |
    export PYTHONPATH=/opt/lib/python
    python3 /opt/scripts/deploy.py --env {{ env_name }}
  environment:
    DB_HOST: "{{ db_host }}"
    API_KEY: "{{ vault_api_key }}"

Idempotency with creates and removes

Make shell commands idempotent:

- name: Build application (skip if already built)
  ansible.builtin.shell: |
    cd /opt/app && make && make install
  args:
    creates: /opt/app/bin/myapp

- name: Clean temporary files (only if they exist) ansible.builtin.shell: rm -rf /tmp/build-* args: removes: /tmp/build-cache

Controlling changed_when

Prevent shell tasks from always showing "changed":

- name: Check disk usage (read-only command)
  ansible.builtin.shell: df -h / | tail -1 | awk '{print $5}'
  register: disk_usage
  changed_when: false

- name: Conditionally mark as changed ansible.builtin.shell: /opt/scripts/update-config.sh register: config_update changed_when: "'Updated' in config_update.stdout"

Choosing a Different Shell

- name: Run with bash
  ansible.builtin.shell: |
    shopt -s globstar
    for f in /var/log/**/*.log; do
      gzip "$f"
    done
  args:
    executable: /bin/bash

- name: Run with zsh ansible.builtin.shell: echo $ZSH_VERSION args: executable: /usr/bin/zsh

Error Handling

- name: Run command and handle failure
  ansible.builtin.shell: /opt/scripts/risky-operation.sh
  register: result
  failed_when: result.rc not in [0, 1]
  ignore_errors: false

- name: Check return code explicitly ansible.builtin.shell: grep -q "READY" /var/run/app.status register: app_status failed_when: false changed_when: false

- name: Act based on result ansible.builtin.debug: msg: "App is {{ 'ready' if app_status.rc == 0 else 'not ready' }}"

Passing stdin Data

- name: Pass input via stdin
  ansible.builtin.shell: mysql -u root -p{{ db_password }} < /tmp/schema.sql
  no_log: true

- name: Use heredoc ansible.builtin.shell: | cat << 'EOF' > /etc/motd Welcome to {{ inventory_hostname }} Managed by Ansible EOF

Security Best Practices

1. Prefer command over shell when possible

# BAD — vulnerable to shell injection
- ansible.builtin.shell: "cat {{ user_file }}"

# GOOD — no shell injection risk - ansible.builtin.command: "cat {{ user_file }}"

2. Use no_log for sensitive commands

- name: Set database password
  ansible.builtin.shell: |
    echo "ALTER USER admin PASSWORD '{{ vault_db_pass }}';" | psql
  no_log: true

3. Quote variables in shell commands

- name: Safe variable usage
  ansible.builtin.shell: 'grep -r "{{ search_term | quote }}" /var/log/'

Common Patterns

Check-then-act

- name: Check if service is running
  ansible.builtin.shell: systemctl is-active myapp
  register: service_check
  changed_when: false
  failed_when: false

- name: Start service if not running ansible.builtin.systemd: name: myapp state: started when: service_check.rc != 0

Pipeline with error checking

- name: Pipeline with pipefail
  ansible.builtin.shell: |
    set -o pipefail
    journalctl -u myapp --since "1 hour ago" | grep -c ERROR
  args:
    executable: /bin/bash
  register: error_count
  changed_when: false
  failed_when: error_count.rc > 1

FAQ

When should I use Ansible shell vs command module?

Use ansible.builtin.shell when you need shell features like pipes (|), redirects (>), environment variable expansion ($HOME), or wildcards (.log). Use ansible.builtin.command for everything else — it's safer because it doesn't process shell metacharacters.

How do I make Ansible shell commands idempotent?

Use the creates parameter to skip the command if a file already exists, or removes to only run if a file exists. You can also use changed_when with conditions based on the command output to accurately report changes.

Why does Ansible shell always show "changed"?

The shell module defaults to changed: true because it can't know if the command actually modified anything. Use changed_when: false for read-only commands, or changed_when: "'specific text' in result.stdout" for conditional change detection.

Can I use bash-specific features in Ansible shell?

Yes, set executable: /bin/bash in the args section to use bash features like shopt, [[ ]] tests, process substitution, and associative arrays that aren't available in /bin/sh.

How do I prevent shell injection in Ansible?

Use the command module instead of shell when possible. If you must use shell, apply the quote filter to user-supplied variables: "{{ user_input | quote }}". Never pass untrusted data directly into shell commands.

Conclusion

The Ansible shell module is powerful for running complex commands with pipes, redirects, and shell features on remote hosts. Always prefer the command module for simple operations, and use shell only when you genuinely need shell capabilities. Apply changed_when, creates, and removes for idempotent automation.

Related Articles

Ansible command Module: Run Shell Commands on Remote HostsAnsible shell vs command vs raw: When to Use Each ModuleAnsible script Module: Run Local Scripts on Remote Hosts

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home