How do I safely pass a Vim variable or the word under the cursor as an argument to a shell command?
Answer
shellescape({expr})
Explanation
The shellescape() function wraps a string in shell-safe quoting, escaping any special characters so it can be embedded in a shell command constructed with :execute '!' . ... or :call system(). Without it, words containing spaces, quotes, dollar signs, or other shell metacharacters silently break the command — or worse, enable unintended execution.
How it works
shellescape({string})returns the string wrapped in single quotes (on Unix/macOS) with internal single quotes properly escaped- An optional second argument (
1) adds extra backslash escaping for use in:!commands, which process backslashes before the shell does - Works in tandem with
expand(),getreg(), and similar functions that yield user-controlled strings
Example
Grep for the exact word under the cursor across all files — safely:
:execute '!grep -rn ' . shellescape(expand('<cword>')) . ' .'
Without shellescape(), a word like it's or foo bar would corrupt the command. Compare:
" UNSAFE — breaks on spaces, quotes, special chars:
:execute '!grep ' . expand('<cword>') . ' ' . expand('%')
" SAFE — handles any content correctly:
:execute '!grep ' . shellescape(expand('<cword>')) . ' ' . shellescape(expand('%'))
Using with system() to capture output into a variable:
let l:result = system('wc -l ' . shellescape(expand('%:p')))
echo l:result
Tips
- Use
shellescape(s, 1)(the1flag) only for:!commands; forsystem()the plain form is correct fnameescape()is the Vim-side counterpart — use it for Ex commands like:editand:read, not for shell commands- Always escape every variable that might contain user input or file paths, even ones that look safe during development
- For building complex shell pipelines, consider assembling the whole command as a string first and echoing it before executing, to verify quoting looks correct