Introduction
Ansible Lint rule 302, deprecated-command-syntax, flags the use of shorthand (free-form) syntax for command-line modules within playbooks. While shorthand is convenient on the command line, it creates hard-to-debug playbooks by mixing commands with module parameters in a single string. This guide shows how to convert shorthand to structured syntax.
The Rule
Rule ID: 302
Name: deprecated-command-syntax
Description: Using command rather than an ideally suited module is acceptable, but shorthand syntax should not be used inside playbooks
Severity: Error
Tags: command-shell, deprecations
Problematic Code
---
- name: Example playbook
hosts: all
tasks:
- name: Perform chmod
ansible.builtin.command: creates=B chmod 644 A # ← shorthand
In this example, creates=B and chmod 644 A are mixed in a single free-form string. Ansible must parse this ambiguously — is creates=B a parameter or part of the command?
More problematic examples:
# Shorthand with multiple parameters
- name: Run script
ansible.builtin.command: chdir=/opt creates=/opt/.done ./install.sh
# Shell shorthand
- name: Check process
ansible.builtin.shell: warn=false ps aux | grep nginx
# Raw shorthand
- name: Remote command
ansible.builtin.raw: executable=/bin/bash echo hello
Correct Code
Convert shorthand parameters to the args block:
---
- name: Example playbook
hosts: all
tasks:
- name: Perform chmod
ansible.builtin.command: chmod 644 A
args:
creates: B
Or use the fully structured format:
- name: Perform chmod
ansible.builtin.command:
cmd: chmod 644 A
creates: B
More Corrections
# BEFORE (shorthand)
- name: Run install script
ansible.builtin.command: chdir=/opt creates=/opt/.done ./install.sh
# AFTER (structured)
- name: Run install script
ansible.builtin.command:
cmd: ./install.sh
chdir: /opt
creates: /opt/.done
# BEFORE (shorthand)
- name: Check process
ansible.builtin.shell: warn=false ps aux | grep nginx
# AFTER (structured)
- name: Check process
ansible.builtin.shell:
cmd: ps aux | grep nginx
# BEFORE (shorthand)
- name: Run remote command
ansible.builtin.raw: executable=/bin/bash echo hello
# AFTER (structured)
- name: Run remote command
ansible.builtin.raw: echo hello
args:
executable: /bin/bash
Affected Modules
This rule applies to all command-line modules that support free-form syntax:
| Module | Common Shorthand Parameters |
|---|---|
ansible.builtin.command | creates, removes, chdir, stdin |
ansible.builtin.shell | creates, removes, chdir, executable |
ansible.builtin.raw | executable |
ansible.builtin.script | creates, removes, chdir, executable |
Why Shorthand Is Problematic
Ambiguity
# Is "creates=/tmp/output" a parameter or part of the command?
ansible.builtin.command: creates=/tmp/output echo "creates=/tmp/output"
Ansible has to guess whether creates= is a module parameter or literal text being passed to the shell.
Hard to Read
# Shorthand — what does this do?
ansible.builtin.command: chdir=/opt creates=/opt/.installed removes=/tmp/installer.sh ./setup.sh --prefix=/usr/local
# Structured — immediately clear
ansible.builtin.command:
cmd: ./setup.sh --prefix=/usr/local
chdir: /opt
creates: /opt/.installed
removes: /tmp/installer.sh
Hard to Maintain
With structured syntax, adding or removing parameters is clean:
# Easy to add/remove parameters
ansible.builtin.command:
cmd: ./deploy.sh
chdir: /opt/app
creates: /opt/app/.deployed # Easy to comment out
# removes: /tmp/deploy-flag # Easy to add back
Complete Example: Before and After
Before (Multiple Shorthand Issues)
---
- name: Deploy application
hosts: all
tasks:
- name: Download
ansible.builtin.command: chdir=/tmp creates=/tmp/app.tar.gz curl -O https://example.com/app.tar.gz
- name: Extract
ansible.builtin.command: chdir=/tmp creates=/opt/app tar xzf app.tar.gz -C /opt/
- name: Install
ansible.builtin.shell: chdir=/opt/app warn=false ./install.sh 2>&1 | tee /var/log/install.log
- name: Cleanup
ansible.builtin.command: removes=/tmp/app.tar.gz rm /tmp/app.tar.gz
After (Structured Syntax)
---
- name: Deploy application
hosts: all
tasks:
- name: Download
ansible.builtin.get_url: # Better: use dedicated module
url: https://example.com/app.tar.gz
dest: /tmp/app.tar.gz
- name: Extract
ansible.builtin.unarchive: # Better: use dedicated module
src: /tmp/app.tar.gz
dest: /opt/
remote_src: true
creates: /opt/app
- name: Install
ansible.builtin.shell:
cmd: ./install.sh 2>&1 | tee /var/log/install.log
chdir: /opt/app
- name: Cleanup
ansible.builtin.file: # Better: use file module
path: /tmp/app.tar.gz
state: absent
Notice: the corrected version also replaces command with more appropriate dedicated modules where possible — another best practice.
Suppressing the Rule
If shorthand is intentional:
- name: Quick command
ansible.builtin.command: creates=/tmp/flag touch /tmp/flag # noqa: deprecated-command-syntax
Or in .ansible-lint:
skip_list:
- deprecated-command-syntax
Related Articles
- Ansible Lint Rule 402: latest[hg]
- Ansible Shell vs Command Module
- Ansible Error Handling Guide
- Ansible Best Practices Guide
- Ansible Debug Module Guide
- Ansible Lint Rule use-loop
- Ansible Troubleshooting: Jinja2 Syntax Errors
Conclusion
Ansible Lint rule 302 (deprecated-command-syntax) catches shorthand syntax that mixes module parameters with command strings. The fix is straightforward: move creates, removes, chdir, and other parameters into the args block or use the key-value format. Structured syntax is unambiguous, easier to read, easier to maintain, and works consistently across all Ansible versions. Even better: check if a dedicated module (like get_url, unarchive, or file) can replace the command/shell call entirely.