63

In shell I have a requirement wherein I have to read the JSON response which is in the following format:

 { "Messages": [ { "Body": "172.16.1.42|/home/480/1234/5-12-2013/1234.toSort", "ReceiptHandle": "uUk89DYFzt1VAHtMW2iz0VSiDcGHY+H6WtTgcTSgBiFbpFUg5lythf+wQdWluzCoBziie8BiS2GFQVoRjQQfOx3R5jUASxDz7SmoCI5bNPJkWqU8ola+OYBIYNuCP1fYweKl1BOFUF+o2g7xLSIEkrdvLDAhYvHzfPb4QNgOSuN1JGG1GcZehvW3Q/9jq3vjYVIFz3Ho7blCUuWYhGFrpsBn5HWoRYE5VF5Bxc/zO6dPT0n4wRAd3hUEqF3WWeTMlWyTJp1KoMyX7Z8IXH4hKURGjdBQ0PwlSDF2cBYkBUA=", "MD5OfBody": "53e90dc3fa8afa3452c671080569642e", "MessageId": "e93e9238-f9f8-4bf4-bf5b-9a0cae8a0ebc" } ] }

Here I am only concerned with the "Body" property value. I made some unsuccessful attempts like:

 jsawk -a 'return this.Body' 

or

 awk -v k="Body" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]} 

But that did not suffice. Can anyone help me with this?

jww
  • 97,681
  • 90
  • 411
  • 885
user1305398
  • 3,550
  • 5
  • 26
  • 42
  • No that is not the option in this case. But for the knowledge purpose please post your comment if you do not mind. – user1305398 Dec 10 '13 at 07:19
  • It is generally not good idea to parse json format with shell oriented tools, see this link for more ideas: http://stackoverflow.com/questions/19511851/parsing-json-data-columnwise-in-shell – tvm Dec 10 '13 at 07:29
  • See also [Unix command-line JSON parser](http://stackoverflow.com/questions/3858671/unix-command-line-json-parser) – koppor Oct 18 '15 at 13:32
  • Are external tools an option? http://kmkeen.com/jshon/ – tokland Dec 10 '15 at 12:02

4 Answers4

106

There is jq for parsing json on the command line:

 jq '.Body'

Visit this for jq: https://stedolan.github.io/jq/

hek2mgl
  • 152,036
  • 28
  • 249
  • 266
user1305398
  • 3,550
  • 5
  • 26
  • 42
16

tl;dr

$ cat /tmp/so.json | underscore select '.Messages .Body' 
["172.16.1.42|/home/480/1234/5-12-2013/1234.toSort"]

Javascript CLI tools

You can use Javascript CLI tools like

Example

Select all name children of a addons:

underscore select ".addons > .name"

The underscore-cli provide others real world examples as well as the json:select() doc.

Édouard Lopez
  • 40,270
  • 28
  • 126
  • 178
7

Similarly using Bash regexp. Shall be able to snatch any key/value pair.

key="Body"
re="\"($key)\": \"([^\"]*)\""

while read -r l; do
    if [[ $l =~ $re ]]; then
        name="${BASH_REMATCH[1]}"
        value="${BASH_REMATCH[2]}"
        echo "$name=$value"
    else
        echo "No match"
    fi
done

Regular expression can be tuned to match multiple spaces/tabs or newline(s). Wouldn't work if value has embedded ". This is an illustration. Better to use some "industrial" parser :)

2

Here is a crude way to do it: Transform JSON into bash variables to eval them.

This only works for:

  • JSON which does not contain nested arrays, and
  • JSON from trustworthy sources (else it may confuse your shell script, perhaps it may even be able to harm your system, You have been warned)

Well, yes, it uses PERL to do this job, thanks to CPAN, but is small enough for inclusion directly into a script and hence is quick and easy to debug:

json2bash() {
perl -MJSON -0777 -n -E 'sub J {
my ($p,$v) = @_; my $r = ref $v;
if ($r eq "HASH") { J("${p}_$_", $v->{$_}) for keys %$v; }
elsif ($r eq "ARRAY") { $n = 0; J("$p"."[".$n++."]", $_) foreach @$v; }
else { $v =~ '"s/'/'\\\\''/g"'; $p =~ s/^([^[]*)\[([0-9]*)\](.+)$/$1$3\[$2\]/;
$p =~ tr/-/_/; $p =~ tr/A-Za-z0-9_[]//cd; say "$p='\''$v'\'';"; }
}; J("json", decode_json($_));'
}

use it like eval "$(json2bash <<<'{"a":["b","c"]}')"

Not heavily tested, though. Updates, warnings and more examples see my GIST.

Update

(Unfortunately, following is a link-only-solution, as the C code is far too long to duplicate here.)

For all those, who do not like the above solution, there now is a C program json2sh which (hopefully safely) converts JSON into shell variables. In contrast to the perl snippet, it is able to process any JSON, as long as it is well formed.

Caveats:

  • json2sh was not tested much.
  • json2sh may create variables, which start with the shellshock pattern () {

I wrote json2sh to be able to post-process .bson with Shell:

bson2json()
{
printf '[';
{ bsondump "$1"; echo "\"END$?\""; } | sed '/^{/s/$/,/';
echo ']';
};

bsons2json()
{
printf '{';
c='';
for a;
do
  printf '%s"%q":' "$c" "$a";
  c=',';
  bson2json "$a";
done;
echo '}';
};

bsons2json */*.bson | json2sh | ..

Explained:

  • bson2json dumps a .bson file such, that the records become a JSON array
    • If everything works OK, an END0-Marker is applied, else you will see something like END1.
    • The END-Marker is needed, else empty .bson files would not show up.
  • bsons2json dumps a bunch of .bson files as an object, where the output of bson2json is indexed by the filename.

This then is postprocessed by json2sh, such that you can use grep/source/eval/etc. what you need, to bring the values into the shell.

This way you can quickly process the contents of a MongoDB dump on shell level, without need to import it into MongoDB first.

Tino
  • 9,583
  • 5
  • 55
  • 60
  • 2
    To the downvoter: What's wrong with my answer? `eval` is not evil in this context. And my answer explains how to access JSON in Shell and Perl. If applied to the JSON-string from the question the wanted result can be found in `${json_Messages_Body[0]}`, just try it, it's just too obvious. And if you downvote because of my warning, well, shoot the messenger, but this still does not change the fact that my warning is true for most shell scripts. **Those, who can write safe scripts, usually do not need to ask on stackoverflow.** So the warning apparently is needed here. – Tino Jun 24 '15 at 23:24
  • 3
    I've been using Perl since 1992, and the code here is *not* immediately obvious to me. You say it is safe, but I don't think that's necessarily true just because you say so. JSON parsers which were not written specifically with security in mind have been responsible for nasty security bugs in the past. If this *was* written with security in mind, please document that in more detail, with credible sources. – tripleee Aug 19 '16 at 03:40
  • 1
    @tripleee: I do **not claim it is safe**. This is clearly stated in the text: "This only works for .. JSON **from trustworthy sources**". (Added "and" now to make it more clear). For example it is **not hardened against shell-shock** and you will see strange errors in case JSON contains nested arrays. If you are puzzled what it does, just run `jsonsource | json2bash`, as it outputs the shell code to stdout. Please note that this code certainly can be improved to create something which can be considered more safe. Doing it safely means a lot more effort than presented here. – Tino Aug 19 '16 at 07:26
  • 1
    You are saying that `eval` is not evil, which implies to me that you consider this safe to use. – tripleee Aug 19 '16 at 07:31
  • In a trustworthy context `eval` is not evil. – Tino Aug 19 '16 at 07:32
  • I've downvoted this answer because the code looks like a cat walked over my keyboard. Unmaintainable. – ssc Oct 18 '16 at 15:00
  • Thanks for your honest words. However when I expand it, then people might downvote, because it gets too lengthy to be used, so they have to minify itself. My recommendation is, expand it yourself, such that you get a feeling for it. Also I think that longer perl script are always a maintainance nightmare, because they look like some [cats had a party on the keyboard](http://blogs.perl.org/users/mithaldu/2011/10/perl-tutorials-suck-and-cause-serious-damage.html) .. (sorry for that pun) ;) – Tino Oct 19 '16 at 16:45