Skip to content
shellmap

Escape a string for safe shell use

Quote / escape a string so it survives word-splitting, glob expansion, and interpretation as multiple arguments — the fix for "spaces in filename break my script" and shell-injection bugs.

How to escape a string for safe shell use in each shell

Bashunix
printf %q "$value"

`%q` produces output that bash can re-parse to the same string (`hello world` → `hello\ world`). Useful for building command lines safely. For "always single-quote, double internal quotes": `q="'${value//\'/\'\\'\'}'"`.

Zshunix
echo "${(q)value}"

Modifier expansion. `${(q)var}` shell-quotes; `${(qq)var}` double-quotes; `${(qqq)var}` single-quotes; `${(qqqq)var}` `$''` ANSI-C. Pick based on what the consumer expects.

Fishunix
string escape -- $value

Produces single-quoted output by default. `string escape --style=script` for shell-script style. Round-trip: `string escape` then `eval (string unescape)` recovers the original string.

PowerShellwindows
[Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($value)

.NET helper since pwsh 3.0. Returns the content safe to embed inside single-quotes. For double-quote contexts use backtick escaping: `$escaped = $value -replace '`','``' -replace '"','`"'`.

cmd.exewindows
echo "%value:"=^"%"

cmd has NO complete escape — its parser is famously broken for `&`, `|`, `<`, `>`, `^`, `"`, `%`. Best practice: switch to PowerShell. The example replaces internal `"` with `^"`; combine with surrounding quotes.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **The bash double-quote vs single-quote semantics matter**: inside `"..."`, `$variable`, `` `cmd` ``/`$(cmd)`, `\$`, `\\`, `\"`, `\``, `\!` are interpreted; everything else is literal. Inside `'...'`, EVERYTHING is literal — no escapes at all (you cannot have a single-quote inside single-quotes; you must end the quoting, escape the quote, and resume: `'don\'t'` is INVALID, the correct form is `'don'\''t'`). The `printf %q` form picks whichever quoting style produces the shortest re-parseable output. For "passing through eval safely", `%q` is the right tool; for "writing a literal string in a shell script", prefer single-quotes whenever possible to remove the escape-vs-not ambiguity entirely.
  • **Anti-pattern: `eval` with non-`%q`-ed user input**: `cmd="rm $file"; eval $cmd` is a SHELL INJECTION. If `$file` is `; rm -rf ~`, you delete the home directory. Defense (besides "don't use eval"): `eval "$(printf "rm %q\n" "$file")"` — `%q` ensures the argument is single-tokenized regardless of contents. Better: never use eval, use arrays. `cmd=(rm "$file"); "${cmd[@]}"` — no eval needed, the array preserves word boundaries. The bash style guide (Google's) bans `eval` outright; in production, `eval` should appear only in tightly-reviewed code with provably-controlled input.
  • **pwsh quoting is DIFFERENT from bash**: in pwsh, `'literal $var'` is literal (no var expansion), `"interpolated $var"` is interpolated, and the escape character is BACKTICK (`` ` ``), not backslash. So `"price: `$10"` produces `price: $10`. To pass a string with quotes to a NATIVE external command: pwsh 7.3+ added `--%` (stop-parsing token) — `& myapp.exe --% --flag "weird value"` passes everything after `--%` to the program literally without pwsh's argument-parsing pass. Without `--%`, the rules are dialect-dependent: cmd.exe-launched native apps get DIFFERENT quoting than direct-execve'd ones — `Start-Process -ArgumentList @("--flag", "weird value")` is the safest universal form (pwsh passes the array directly via Win32 `CreateProcess`).
  • **Filenames with NEWLINES** are the worst case — `\n` is a valid character in POSIX filenames (`touch $'evil\nfilename.txt'`). `for f in *.txt; do` works because globbing returns an array; `for f in $(ls)` BREAKS because the newline inside the filename gets treated as a separator. The robust idiom: `find . -name "*.txt" -print0 | xargs -0 …` (null-byte separator — null is the ONE character that can't appear in a filename). pwsh handles this natively — `Get-ChildItem` returns objects, not strings, so newlines in filenames are preserved through pipelines without quoting concerns.
  • **Round-tripping**: a complete shell-escape round-trip needs to handle: spaces, tabs, newlines, all 7 ASCII control chars below space, `*`, `?`, `[`, `]`, `{`, `}`, `~`, `!`, `&`, `|`, `;`, `<`, `>`, `(`, `)`, `$`, `` ` ``, `"`, `'`, `\`, plus UTF-8 multi-byte sequences. Bash `printf %q` handles all of these correctly. Hand-rolled regex-based escapers ALMOST ALWAYS miss something (most commonly: backslashes in the input, or non-ASCII bytes that get mangled). Don't roll your own — `%q` exists for a reason. For Python: `shlex.quote()`. For Node.js: the `shell-quote` package. The "escape-by-hand" reflex is the source of half of all shell-injection CVEs.

Related commands

Related tasks