Pipe Lookup — Run Command And Return Output Locally
pipe is the most powerful lookup in this section, and the most dangerous one — it runs an arbitrary command on the control node, through a real shell, and hands back whatever it printed.
Running A Local Command With lookup('pipe', ...)
msg: "{{ lookup('pipe', 'date') }}"Runs date — on the control node, following the same rule established over the last two chapters — captures its output, and returns it as a string with the trailing newline stripped.
A Realistic Example: Embedding A Local Git Commit Hash
A genuinely common practical use — tagging a deployment with the exact commit it was deployed from, read straight from the control node’s own local repository:
vars:
git_commit: "{{ lookup('pipe', 'git rev-parse --short HEAD') }}"
tasks:
- name: Tag deployment
ansible.builtin.debug:
msg: "Deploying commit {{ git_commit }}"The Real Danger: Command Injection
pipe runs its command through an actual shell — meaning shell metacharacters in whatever string you build aren’t inert text, they’re live syntax, exactly the same danger eval-style command construction carries in any other context.
vars:
user_supplied: "test; echo INJECTED"
tasks:
- name: Vulnerable pipe usage
ansible.builtin.debug:
msg: "{{ lookup('pipe', 'echo ' + user_supplied) }}"test
INJECTEDuser_supplied was never meant to be anything but a piece of text — but concatenated directly into the command string, its semicolon became a genuine shell command separator. The shell ran two commands instead of one: echo test, then echo INJECTED. Whatever was embedded in that string got executed, not just printed.
The Fix: quote
This is exactly what quote, from the manipulating-data section’s strings chapter, exists for:
vars:
user_supplied: "test; echo INJECTED"
tasks:
- name: Safer pipe usage
ansible.builtin.debug:
msg: "{{ lookup('pipe', 'echo ' + (user_supplied | quote)) }}"test; echo INJECTEDThe entire string — semicolon and all — now prints as a single, literal, inert piece of text. quote wrapped it in shell-safe quoting before it ever reached the command string, so the shell sees one safely-escaped argument instead of a command followed by a second, separate command.
Best Practices
- Never build a
pipecommand string by directly concatenating untrusted or variable data. Pass it through| quotefirst, without exception. - Treat
lookup('pipe', ...)with the same caution as any other command-injection risk — it genuinely runs through a real shell, so unquoted variable data inside it is live syntax, not inert text. - Prefer
pipefor mostly-static, control-node-side commands — a git commit hash, a local tool’s version — over commands dynamically assembled from variable or user-supplied input, whenever there’s a safer alternative available.