Escape JSON for Bash Shell Scripts — Practical Patterns
- Bash single quotes are the safest container — no escapes needed for double quotes inside.
- For dynamic JSON, jq -n -arg builds JSON from variables without escape errors.
- Heredocs let you write multi-line JSON without quoting at all.
Table of Contents
Embedding JSON inside a bash script is one of those problems that looks simple until you have a string with a single quote, an apostrophe, or a variable that needs to interpolate. Three patterns cover almost every case: single-quoted literals, heredocs, and jq for dynamic generation.
The Single-Quote Pattern (Static JSON)
For JSON that doesn't change between runs, single-quote the whole thing:
BODY='{"name": "Alex", "active": true}'
curl -X POST -d "$BODY" https://api.example.com/users
Bash treats everything inside single quotes as literal. Double quotes, backslashes, dollar signs — all preserved exactly. The only problem character is the single quote itself, which can't appear inside single-quoted text.
If your JSON has single quotes, the workaround is the famous '\'' sequence:
BODY='{"name": "O'\''Brien"}'
That's: close the single-quoted string, escape a literal single quote, reopen the single-quoted string. Ugly, but standard.
Heredocs for Multi-Line JSON
For longer or multi-line JSON, a heredoc avoids quoting entirely:
BODY=$(cat <<'EOF'
{
"name": "Alex",
"bio": "She said \"hi\".",
"tags": ["a", "b"]
}
EOF
)
curl -d "$BODY" https://api.example.com/users
The single quotes around the opening 'EOF' matter — they tell bash NOT to interpolate variables or expand backticks inside the heredoc. Without them, every $ in your JSON gets eaten.
Use double-quoted EOF (or no quotes) when you DO want variable interpolation:
USER_NAME="Alex"
BODY=$(cat <<EOF
{ "name": "$USER_NAME" }
EOF
)
Variables expand, but you've also lost protection from accidental shell expansion of any $ in the JSON.
Build JSON Dynamically With jq
For JSON that mixes shell variables and literal structure, hand-escaping gets fragile fast. jq solves this:
NAME="O'Brien" EMAIL="[email protected]" BODY=$(jq -n \ --arg name "$NAME" \ --arg email "$EMAIL" \ '{name: $name, email: $email}') # BODY is now valid JSON: {"name":"O'Brien","email":"[email protected]"} curl -d "$BODY" https://api.example.com/users
jq handles all the escaping. Your variables can contain quotes, backslashes, newlines — anything — and jq produces valid JSON. This is the right pattern for any script that builds JSON from user input or dynamic values.
For arrays of values, use --argjson to pass already-JSON values, or build the array structure in the jq filter.
Reading JSON Back Out of curl Responses
The reverse direction — extracting values from a JSON response — also benefits from jq:
RESPONSE=$(curl -s https://api.example.com/users/1) NAME=$(echo "$RESPONSE" | jq -r '.name') echo "User name: $NAME"
The -r flag (raw) strips the outer quotes from string values. Without it, $NAME would be "Alex" with literal quotes; with it, $NAME is just Alex.
For nested values: jq -r '.user.profile.email'. For array iteration: jq -r '.users[].name'. For tabular extraction: jq -r '.users[] | [.id, .name] | @tsv'.
jq is worth learning if you do any meaningful shell scripting against APIs. The syntax is its own thing but it pays for itself within a few scripts.
When to Stop Fighting the Shell
If your script has more than ~10 lines of JSON manipulation, the shell is probably the wrong tool. Three signs to switch:
- You're escaping the same characters in three different places
- You're using
sedto manipulate JSON (don't — use jq) - You're chaining four heredocs together
Move to Python, Node, or any real language with proper JSON support. Bash is great for orchestration; it's terrible for serious data manipulation. The line where it stops being worth it is somewhere around "I need to merge two JSON objects."
Escape JSON for Bash
Paste your JSON, get a shell-safe escaped version. Free, browser-based.
Open Free JSON Escape / Unescape ToolFrequently Asked Questions
Why does my bash variable break the JSON?
Usually unquoted expansion. Always wrap variables in double quotes when used inside JSON: "$VAR" not $VAR.
How do I escape a single quote in a single-quoted bash string?
Use the sequence '\\'\' — close, escape, reopen. Or use a heredoc with no quotes.
Can I use jq to escape arbitrary text for JSON?
Yes: echo "your text" | jq -Rs . produces a properly escaped JSON string value.
What about Windows Subsystem for Linux?
Same bash rules apply inside WSL. Pure Windows cmd or PowerShell uses different quoting — see the curl PowerShell guide.

