Escaping characters in bash (for JSON)

JsonBashEscaping

Json Problem Overview


I'm using git, then posting the commit message and other bits as a JSON payload to a server.

Currently I have:

MSG=`git log -n 1 --format=oneline | grep -o ' .\+'`

which sets MSG to something like:

Calendar can't go back past today

then

curl -i -X POST \
  -H 'Accept: application/text' \
  -H 'Content-type: application/json' \
  -d "{'payload': {'message': '$MSG'}}" \
  'https://example.com'

My real JSON has another couple of fields.

This works fine, but of course when I have a commit message such as the one above with an apostrophe in it, the JSON is invalid.

How can I escape the characters required in bash? I'm not familiar with the language, so am not sure where to start. Replacing ' with \' would do the job at minimum I suspect.

Json Solutions


Solution 1 - Json

jq can do this.

Lightweight, free, and written in C, jq enjoys widespread community support with over 15k stars on GitHub. I personally find it very speedy and useful in my daily workflow.

Convert string to JSON

$ echo -n '猫に小判' | jq -aRs .
"\u732b\u306b\u5c0f\u5224"

Note parameter -n to suppress new line at the end of string.

$ printf 'ô\nè\nà\n' | jq -Rs .
\nè\nà\n"

To explain,

  • -a means "ascii output" (omitted in the second example)
  • -R means "raw input"
  • -s means "include linebreaks" (mnemonic: "slurp")
  • . means "output the root of the JSON document"

Git + Grep Use Case

To fix the code example given by the OP, simply pipe through jq.

MSG=`git log -n 1 --format=oneline | grep -o ' .\+' | jq -aRs .`

Solution 2 - Json

Using Python:

This solution is not pure bash, but it's non-invasive and handles unicode.

json_escape () {
    printf '%s' "$1" | python -c 'import json,sys; print(json.dumps(sys.stdin.read()))'
}

Note that JSON is part of the standard python libraries and has been for a long time, so this is a pretty minimal python dependency.

Or using PHP:

json_escape () {
    printf '%s' "$1" | php -r 'echo json_encode(file_get_contents("php://stdin"));'
}

Use like so:

$ json_escape "ヤホー"
"\u30e4\u30db\u30fc"

Solution 3 - Json

Instead of worrying about how to properly quote the data, just save it to a file and use the @ construct that curl allows with the --data option. To ensure that the output of git is correctly escaped for use as a JSON value, use a tool like jq to generate the JSON, instead of creating it manually.

jq -n --arg msg "$(git log -n 1 --format=oneline | grep -o ' .\+')" \
   '{payload: { message: $msg }}' > git-tmp.txt

curl -i -X POST \
  -H 'Accept: application/text' \
  -H 'Content-type: application/json' \
  -d @git-tmp.txt \
  'https://example.com'

You can also read directly from standard input using -d @-; I leave that as an exercise for the reader to construct the pipeline that reads from git and produces the correct payload message to upload with curl.

(Hint: it's jq ... | curl ... -d@- 'https://example.com' )

Solution 4 - Json

I was also trying to escape characters in Bash, for transfer using JSON, when I came across this. I found that there is actually a larger list of characters that must be escaped – particularly if you are trying to handle free form text.

There are two tips I found useful:

  • Use the Bash ${string//substring/replacement} syntax described in this thread.
  • Use the actual control characters for tab, newline, carriage return, etc. In vim you can enter these by typing Ctrl+V followed by the actual control code (Ctrl+I for tab for example).

The resultant Bash replacements I came up with are as follows:

JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\\/\\\\} # \ 
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\//\\\/} # / 
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\'/\\\'} # ' (not strictly needed ?)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\"/\\\"} # " 
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//   /\\t} # \t (tab)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//
/\\\n} # \n (newline)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^M/\\\r} # \r (carriage return)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^L/\\\f} # \f (form feed)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^H/\\\b} # \b (backspace)

I have not at this stage worked out how to escape Unicode characters correctly which is also (apparently) required. I will update my answer if I work this out.

Solution 5 - Json

OK, found out what to do. Bash supports this natively as expected, though as always, the syntax isn't really very guessable!

Essentially ${string//substring/replacement} returns what you'd image, so you can use

MSG=${MSG//\'/\\\'}

To do this. The next problem is that the first regex doesn't work anymore, but that can be replaced with

git log -n 1 --pretty=format:'%s'

In the end, I didn't even need to escape them. Instead, I just swapped all the ' in the JSON to ". Well, you learn something every day.

Solution 6 - Json

git log -n 1 --format=oneline | grep -o ' .\+' | jq --slurp --raw-input

The above line works for me. refer to https://github.com/stedolan/jq for more jq tools

Solution 7 - Json

I found something like that :

MSG=`echo $MSG | sed "s/'/\\\\\'/g"`

Solution 8 - Json

The simplest way is using [jshon][1], a command line tool to parse, read and create JSON.

jshon -s 'Your data goes here.' 2>/dev/null

[1]: http://kmkeen.com/jshon/ "Jshon"

Solution 9 - Json

> [...] with an apostrophe in it, the JSON is invalid.

Not according to https://www.json.org. A single quote is allowed in a JSON string. > How can I escape the characters required in bash?

You can use [tag:xidel] to properly prepare the JSON you want to POST.
As https://example.com can't be tested, I'll be using https://api.github.com/markdown (see this answer) as an example.

Let's assume 'çömmít' "mêssågè" as the exotic output of git log -n 1 --pretty=format:'%s'.

Create the (serialized) JSON object with the value of the "text"-attribute properly escaped:

$ git log -n 1 --pretty=format:'%s' | \
  xidel -se 'serialize({"text":$raw},{"method":"json","encoding":"us-ascii"})'
{"text":"'\u00E7\u00F6mm\u00EDt' \"m\u00EAss\u00E5g\u00E8\""}

Curl (variable)

$ eval "$(
  git log -n 1 --pretty=format:'%s' | \
  xidel -se 'msg:=serialize({"text":$raw},{"method":"json","encoding":"us-ascii"})' --output-format=bash
)"

$ echo $msg
{"text":"'\u00E7\u00F6mm\u00EDt' \"m\u00EAss\u00E5g\u00E8\""}

$ curl -d "$msg" https://api.github.com/markdown
<p>'çömmít' "mêssågè"</p>

Curl (pipe)

$ git log -n 1 --pretty=format:'%s' | \
  xidel -se 'serialize({"text":$raw},{"method":"json","encoding":"us-ascii"})' | \
  curl -d@- https://api.github.com/markdown
<p>'çömmít' "mêssågè"</p>

Actually, there's no need for curl if you're already using xidel.

Xidel (pipe)

$ git log -n 1 --pretty=format:'%s' | \
  xidel -s \
  -d '{serialize({"text":read()},{"method":"json","encoding":"us-ascii"})}' \
  "https://api.github.com/markdown" \
  -e '$raw'
<p>'çömmít' "mêssågè"</p>

Xidel (pipe, in-query)

$ git log -n 1 --pretty=format:'%s' | \
  xidel -se '
    x:request({
      "post":serialize(
        {"text":$raw},
        {"method":"json","encoding":"us-ascii"}
      ),
      "url":"https://api.github.com/markdown"
    })/raw
  '
<p>'çömmít' "mêssågè"</p>

Xidel (all in-query)

$ xidel -se '
  x:request({
    "post":serialize(
      {"text":system("git log -n 1 --pretty=format:'\''%s'\''")},
      {"method":"json","encoding":"us-ascii"}
    ),
    "url":"https://api.github.com/markdown"
  })/raw
'
<p>'çömmít' "mêssågè"</p>

Solution 10 - Json

I struggled with the same problem. I was trying to add a variable on the payload of cURL in bash and it kept returning as invalid_JSON. After trying a LOT of escaping tricks, I reached a simple method that fixed my issue. The answer was all in the single and double quotes:

 curl --location --request POST 'https://hooks.slack.com/services/test-slack-hook' \
--header 'Content-Type: application/json' \
--data-raw '{"text":'"$data"'}'

Maybe it comes in handy for someone!

Solution 11 - Json

I had the same idea to send a message with commit message after commit. First i tryed similar was as autor here. But later found a better and simpler solution.

Just created php file which is sending message and call it with wget. in hooks/post-receive :

wget -qO - "http://localhost/git.php" 

in git.php:

chdir("/opt/git/project.git");
$git_log = exec("git log -n 1 --format=oneline | grep -o ' .\+'");

And then create JSON and call CURL in PHP style

Solution 12 - Json

This is an escaping solution using Perl that escapes backslash (\), double-quote (") and control characters U+0000 to U+001F:

$ echo -ne "Hello, 🌵\n\tBye" | \
  perl -pe 's/(\\(\\\\)*)/$1$1/g; s/(?!\\)(["\x00-\x1f])/sprintf("\\u%04x",ord($1))/eg;'
Hello, 🌵\u000a\u0009Bye

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRich BradshawView Question on Stackoverflow
Solution 1 - JsonjchookView Answer on Stackoverflow
Solution 2 - Jsonpolm23View Answer on Stackoverflow
Solution 3 - JsonchepnerView Answer on Stackoverflow
Solution 4 - JsonxsgordonView Answer on Stackoverflow
Solution 5 - JsonRich BradshawView Answer on Stackoverflow
Solution 6 - JsonwcyView Answer on Stackoverflow
Solution 7 - JsonDmitry KoroliovView Answer on Stackoverflow
Solution 8 - Jsonm-szalikView Answer on Stackoverflow
Solution 9 - JsonReinoView Answer on Stackoverflow
Solution 10 - JsonIoana Adelina ApetreiView Answer on Stackoverflow
Solution 11 - JsonKirillDEView Answer on Stackoverflow
Solution 12 - JsonJosh BodeView Answer on Stackoverflow