Bash has no built-in JSON support, and trying to parse JSON with grep, sed, or awk is fragile and breaks the moment formatting changes. The right tool is jq. This guide shows how to parse JSON in shell scripts reliably.

Why not grep or sed?

JSON is a structured format with nesting, escaping, and flexible whitespace. Regex-based tools cannot reliably understand that structure — a script that works today breaks when the API minifies its output or reorders fields tomorrow. Use a real JSON parser.

Install jq

# macOS
brew install jq
# Ubuntu / Debian
sudo apt install jq

Extract a single field

echo '{"name": "Alice", "age": 30}' | jq '.name'
# "Alice"

# Use -r for raw output (no quotes)
echo '{"name": "Alice"}' | jq -r '.name'
# Alice

Assign a JSON value to a shell variable

This is the most common need. Always use -r so you do not get quotes in your variable:

NAME=$(echo "$JSON" | jq -r '.name')
TOKEN=$(curl -s https://api.example.com/auth | jq -r '.token')

echo "Hello, $NAME"

Parse a file

jq -r '.database.host' config.json

# Or read it in
HOST=$(jq -r '.database.host' config.json)

Validate your JSON before scripting

Paste your JSON to confirm it is valid and explore its structure — so your jq paths are correct the first time. Free and private.

Open tool →

Loop over a JSON array

The safe pattern uses -c to emit one compact object per line, then reads them in a while loop:

echo "$JSON" | jq -c '.users[]' | while read -r user; do
  name=$(echo "$user" | jq -r '.name')
  echo "User: $name"
done

To loop over just one field:

jq -r '.users[].email' data.json | while read -r email; do
  echo "Sending to $email"
done

Filter inside a script

# Only active users
jq -r '.users[] | select(.active == true) | .name' data.json

# Count array elements
COUNT=$(jq '.items | length' data.json)
echo "Found $COUNT items"

Handle API responses safely

response=$(curl -s -w "%{http_code}" https://api.example.com/data)
http_code="${response: -3}"
body="${response%???}"

if [ "$http_code" -eq 200 ]; then
  echo "$body" | jq -r '.result'
else
  echo "Request failed: $http_code"
fi

Check if a field exists

if echo "$JSON" | jq -e '.error' > /dev/null; then
  echo "Response contains an error"
fi

The -e flag sets the exit code based on the result, which is perfect for if statements.

Common mistakes

  • Forgetting -r: your variables end up wrapped in quotes.
  • Parsing with grep: works until the JSON formatting changes — then it silently breaks.
  • Not quoting variables: always use "$VAR" to handle spaces and special characters.