I'm trying to create a JSON in BASH where one of the fields is based on the result of an earlier command

BIN=$(cat next_entry)
echo $OUTDIR
JSON="'"'{"hostname": "localhost", "outdir": "${OUTDIR}", "port": 20400, "size": 100000}'"'"
echo $JSON

The above script when executed, returns:

, port: 20400, size: 100000}': /tmp/cpupower/0

How can I properly substitute variables inside these multi-quoted strings?

  • 1
    I think I just saw a hookah-smoking caterpillar and a grinning cat. (1) I don’t see any way that the commands presented in the question could have produced the output presented in the question.  (Note that the OUTDIR variable appears to be expanded in the value of JSON, and the quotes around "port" and "size" are inexplicably absent.)  (2) It seems obvious to me that the BIN variable is getting a carriage return in it (from the next_entry file); and yet the problem apparently went away without that issue being addressed. May 25, 2019 at 6:53

6 Answers 6

JSON=\''{"hostname": "localhost", "outdir": "'"$OUTDIR"'", "port": 20400, "size": 100000}'\'

That is get out of the single quotes for the expansion of $OUTDIR. We did put that expansion inside double-quotes for good measure even though for a scalar variable assignment it's not strictly necessary.

When you're passing the $JSON variable to echo, quotes are necessary though to disable the split+glob operator. It's also best to avoid echo for arbitrary data:

printf '%s\n' "$JSON"
  • 1
    Cool. This works! Could you perhaps explain a little more on why this works? What is the significance of the first \'. I don't believe I've seen that on some of the examples I've seen on SO/unix.stackexchange. Sep 27, 2016 at 14:29
  • Backslash before ' tells the shell to treat ' as any ordinary character, instead of interpreting it as a string delimiter. The backslash can be used in front of a few other characters. For example, \\ will represent the \ character itself and \" means the " character literary. See for example the \n in above printf argument, where printf interprets \n as the line-feed character.
    – Rein
    Sep 30, 2016 at 16:47
  • 2
    (1) I believe that you should have been clearer that the change from "'" to \' was a totally stylistic (i.e., arbitrary) one, and that the only substantive change you made was the quotes around $OUTDIR. (You might also have mentioned that the braces in ${OUTDIR} simply were not necessary.)  (2) Please see my comment on the question and tell me whether I’m hallucinating (i.e., did I overlook something, or did you?). May 25, 2019 at 6:53
  • @G-Man, I agree with you. May 25, 2019 at 7:03

If you ended up here trying to use AWS commands, @Stéphane Chazelas's answer almost works. In here, the initial escaped quotes (\') are not necessary, they actualy break the command.

IP=$(curl ipecho.net/plain ; echo)

aws ec2 authorize-security-group-ingress --group-id sg-**************** \
    --ip-permissions '[{"IpProtocol": "tcp", "FromPort": 15000, "ToPort": 15000, "IpRanges": [{"CidrIp": "'"$IP/32"'", "Description": "Service A"}]}]'

^ This works just fine


Stéphane's answer is great, and upvoted. Here's just a tip; instead of doing

BIN=$(cat next_entry)

You can do:


And thus save spawning an extra process. Read more here.


The contents of the string in $BIN likely contains DOS-style newlines, with a carriage return character before each newline character. When printing this to the terminal, the carriage return character(s) moves the cursor back to start of the line, which is why your output looks a bit strange.

Another consequence of this is that your JSON document will be invalid as carriage returns (and newlines) will need special encoding. You also can't insert any character into a JSON value as some characters, notably quoting characters, need to be encoded.

I would suggest two things:

  1. Convert your data file to Unix text format using the dos2unix conversion tool.

  2. Use a command line tool to create your JSON document that knows how to encode data for JSON. The jo utility is one such tool.

Using jo:


bin=$(cat next_entry)

json=$( jo hostname=localhost outdir="$outdir" port=20400 size=100000 )

printf '%s\n' "$json"

Given a file next_entry with the contents

line 1
line 2

the script above would output

{"hostname":"localhost","outdir":"/tmp/cpupower/line 1\nline 2","port":20400,"size":100000}

If jo is given its -p option for pretty-printing the constructed JSON document:

   "hostname": "localhost",
   "outdir": "/tmp/cpupower/line 1\nline 2",
   "port": 20400,
   "size": 100000

If I have a json file that goes like eating.json = {"diner" : {"time" : ""}}, the way I do it is the following :

declare -a NOW
jq '.diner.time = "'"${NOW}"'"' eating.json > tmp.$$.json && mv tmp.$$.json eating.json

So, first, I created my variable, then, I put the value I wanted in it, and, finally, using jq, I made the modification in a temporary file (specificities of the jq module I believe), and then put the modifications back in eating.json.


Best solution I've seen in terms of readability (and I take no credit for this) is here:


Copying the key part of that solution here:

json=$(cat <<-END
        "tag_name": "${version}", 
        "target_commitish": "${branch}", 
        "name": "${title}", 
        "body": "${notes}", 
        "draft": ${is_draft}, 
        "prerelease": ${is_prerelease} 

This uses a here document to allow a readable multi-line JSON block with variable substitution while preserving the double quotes. The here doc is redirected to the cat command, which is wrapped in $() to put the output into the json variable.

  • Please consider editing your answer to include at least a summary of the linked explanation. Link-only answers are discouraged, as the link may become invalid or the linked content change.
    – AdminBee
    Jul 23, 2021 at 7:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .