About

When the JSON string to be parsed is meant to be piped on and used just on the command line, then js and jsawk make good tools however, parsing JSON from within a script can be cumbersome when the script is to be tranferred to other machines that may not have js or jsawk installed.

Some solutions do exist:

  • One pure Bash parser for JSON is JSON.sh but the script relies on external tools that may not be available (such as egrep, gawk).
  • A variant closer to POSIX is bash-json-parser but the script requires Bash and will not run under Dash (which is closer to POSIX than Bash and is the preferred default shell for modern distributions).

Translating from Bash

Some of the syntax and some semantics change from bash to dash but not much such that the second script by Florian Kalis (the author of bash-json-parser) can just be adjusted in order to be able to run under dash. A good resource is perhaps GreyCat's bashism page that extensively documents the extensions to the command shell that are particular to Bash.

Without further ado, the following script should be compatible with dash and calling the json function will output via echo key and value pairs to the standard output.

json_bash_mini.sh
#!/usr/bin/env sh
###########################################################################
## Original @ https://github.com/fkalis/bash-json-parser                 ##
## Modifications by Wizardry and Steamworks:                             ##
##   * dash compatibility                                                ##
###########################################################################
## conversion from bash with the following changes:                      ##
##   * parse "" "" <<< "${INPUT}" -> echo "${INPUT}" | parse "" ""       ##
##   * == -> =                                                           ##
##   * read -r -s -n 1 c -> readc c                                      ##
###########################################################################
 
json_readc() {
	if [ -t 0 ]; then
		saved_tty_settings=$(stty -g);
		stty -icanon -echo min 1 time 0;
	fi;
	c=$(dd bs=1 count=1 conv=noerror,sync 2>/dev/null);
	if [ -t 0 ]; then
		stty "$saved_tty_settings";
	fi;
};
json_set_entry() {
	echo "$1=$2";
};
json_parse_array() {
	local current_path="${1:+$1.}$2";
	local current_scope="root";
	local current_index=0;
	while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
		[ "$preserve_current_char" = "0" ] && chars_read=$((chars_read + 1)) && json_readc c;
		preserve_current_char=0;
		c=${c:-' '};
		case "$current_scope" in
		"root") 
			case "$c" in
			'{')
                json_parse_object "$current_path" "$current_index";
				current_scope="entry_separator";
				;;
			']')
				return;
				;;
			[\"tfTF\-0-9])
				preserve_current_char=1;
				json_parse_value "$current_path" "$current_index";
				preserve_current_char=1;
				current_scope="entry_separator";
				;;
			esac
			;;
		"entry_separator")
			[ "$c" = "," ] && current_index=$((current_index + 1)) && current_scope="root";
			[ "$c" = "]" ] && return;
			;;
		esac
	done
};
json_parse_value() {
	local current_path="${1:+$1.}$2";
	local current_scope="root";
	while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
		[ "$preserve_current_char" = "0" ] && chars_read=$((chars_read + 1)) && json_readc c;
		preserve_current_char=0;
		c=${c:-' '};
		case "$current_scope" in
		"root") 
			case "$c" in
			'"') 
				current_scope="string";
				current_varvalue="";
				;;
			[\-0-9]) 
				current_scope="number";
				current_varvalue="$c";
				;;
			[tfTF]) 
				current_scope="boolean";
				current_varvalue="$c";
				;;
			"[") 
				json_parse_array "" "$current_path";
				return;
				;;
			"{") 
				json_parse_object "" "$current_path";
				return;
				;;
			esac
			;;
		"string") 
			case "$c" in
			'"') 
				[ "$current_escaping" = "0" ] && json_set_entry "$current_path" "$current_varvalue" && return;
				[ "$current_escaping" = "1" ] && current_varvalue="$current_varvalue$c" && current_escaping=0;
				;;
			'\') 
				[ "$current_escaping" = "1" ] && current_varvalue="$current_varvalue$c";
				current_escaping=$((1 - current_escaping));
				;;
			*) 
				current_escaping=0;
				current_varvalue="$current_varvalue$c";
				;;
			esac
			;;
		"number") 
			case "$c" in
			[,\]}]) 
				json_set_entry "$current_path" "$current_varvalue";
				preserve_current_char=1;
				return
				;;
			[\-0-9.]) 
				current_varvalue="$current_varvalue$c";
				;;
 
			esac
			;;
		"boolean") 
			case "$c" in
			[,\]}]) 
				json_set_entry "$current_path" "$current_varvalue";
				preserve_current_char=1;
				return;
				;;
			[a-zA-Z]) 
				current_varvalue="$current_varvalue$c";
				;;
			esac
			;;
		esac
	done
};
json_parse_object() {
	local current_path="${1:+$1.}$2";
	local current_scope="root";
	while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
		[ "$preserve_current_char" = "0" ] && chars_read=$((chars_read + 1)) && json_readc c;
		preserve_current_char=0;
		c=${c:-' '};
		case "$current_scope" in
		"root") 
			[ "$c" = "}" ] && return;
			[ "$c" = "\"" ] && current_scope="varname" && current_varname="" && current_escaping=0;
			;;
		"varname")
			case "$c" in
			'"') 
				[ "$current_escaping" = "0" ] && current_scope="key_value_separator";
				[ "$current_escaping" = "1" ] && current_varname="$current_varname$c" && current_escaping=0;
				;;
			'\') 
				current_escaping=$((1 - current_escaping));
				current_varname="$current_varname$c";
				;;
			*) 
				current_escaping=0;
				current_varname="$current_varname$c";
				;;
			esac
			;;
		"key_value_separator")
			[ "$c" = ":" ] && json_parse_value "$current_path" "$current_varname" && current_scope="field_separator";
			;;
		"field_separator") 
			[ "$c" = ',' ] && current_scope="root";
			[ "$c" = '}' ] && return;
			;;
		esac
	done
};
json_read() {
	chars_read=0;
	preserve_current_char=0;
	while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
		json_readc c;
		c=${c:-' '};
		chars_read=$((chars_read + 1));
		[ "$c" = "{" ] && json_parse_object "" "" && return;
		[ "$c" = "[" ] && json_parse_array "" "" && return;
	done
};
 
json() {
	if [ -z "$@" ]; then
		INPUT=$(cat -);
	else
		INPUT=$(echo "$@");
	fi;
	INPUT_LENGTH="${#INPUT}";
	echo "${INPUT}" | json_read;
};
 
json "$@"

The function json_readc is used due to limiting POSIX capabilities that make it very difficult to read one and only one character from standard input. Even though it seems that json_readc could be simplified with head -c 1, the -c parameter is not defined in POSIX but rather a GNU addition.

More ample testing was performed, but re-using the exact same example as on the bash-json-parser page, the same parameters yield the exact same result:

# ./json_bash_mini.sh  '{ "a": "b", "c": [ "hallo", "welt" ] }'
a=b
c.0=hallo
c.1=welt

with the main difference that json_bash_mini.sh will also work under ash, dash and zsh.

Usage in Scripts

Since carrying around a large library of scripts is not always feasible, the functions can be compacted (or minified) to a minimum and a very ugly one-liner.

r() { if [ -t 0 ]; then b=$(stty -g); stty -icanon -echo min 1 time 0; fi; c=$(dd bs=1 count=1 conv=noerror,sync 2>/dev/null); if [ -t 0 ]; then stty "$b"; fi; }; e() { echo "$1=$2"; }; a() { local d="${1:+$1.}$2"; local f="u"; local g=0; while [ "$h" -lt "$j" ]; do [ "$i" = "0" ] && h=$((h + 1)) && r c; i=0; c=${c:-' '}; case "$f" in "u") case "$c" in '{') o "$d" "$g"; f="l"; ;; ']') return; ;; [\"tfTF\-0-9]) i=1; v "$d" "$g"; i=1; f="l"; ;; esac ;; "l") [ "$c" = "," ] && g=$((g + 1)) && f="u"; [ "$c" = "]" ] && return; ;; esac done }; v() { local d="${1:+$1.}$2"; local f="u"; while [ "$h" -lt "$j" ]; do [ "$i" = "0" ] && h=$((h + 1)) && r c; i=0; c=${c:-' '}; case "$f" in "u") case "$c" in '"') f="z"; k=""; ;; [\-0-9]) f="y"; k="$c"; ;; [tfTF]) f="x"; k="$c"; ;; "[") a "" "$d"; return; ;; "{") o "" "$d"; return; ;; esac ;; "z") case "$c" in '"') [ "$m" = "0" ] && e "$d" "$k" && return; [ "$m" = "1" ] && k="$k$c" && m=0; ;; '\') [ "$m" = "1" ] && k="$k$c"; m=$((1 - m)); ;; *) m=0; k="$k$c"; ;; esac ;; "y") case "$c" in [,\]}]) e "$d" "$k"; i=1; return ;; [\-0-9.]) k="$k$c"; ;; esac ;; "x") case "$c" in [,\]}]) e "$d" "$k"; i=1; return; ;; [a-zA-Z]) k="$k$c"; ;; esac ;; esac done }; o() { local d="${1:+$1.}$2"; local f="u"; while [ "$h" -lt "$j" ]; do [ "$i" = "0" ] && h=$((h + 1)) && r c; i=0; c=${c:-' '}; case "$f" in "u") [ "$c" = "}" ] && return; [ "$c" = "\"" ] && f="w" && n="" && m=0; ;; "w") case "$c" in '"') [ "$m" = "0" ] && f="p"; [ "$m" = "1" ] && n="$n$c" && m=0; ;; '\') m=$((1 - m)); n="$n$c"; ;; *) m=0; n="$n$c"; ;; esac ;; "p") [ "$c" = ":" ] && v "$d" "$n" && f="q"; ;; "q") [ "$c" = ',' ] && f="u"; [ "$c" = '}' ] && return; ;; esac done }; s() { h=0; i=0; while [ "$h" -lt "$j" ]; do r c; c=${c:-' '}; h=$((h + 1)); [ "$c" = "{" ] && o "" "" && return; [ "$c" = "[" ] && a "" "" && return; done }; json() { if [ -z "$@" ]; then t=$(cat -); else t=$(echo "$@"); fi; j="${#t}"; echo "${t}" | s; }

Note that the only user-defined string in the one-liner is json which represents the method through which a JSON string will be decoded to key-value tuples.

Since that is not near enough and if including the script guerilla style is preferred, then the one-liner can be base64 encoded, included in the script that requires a json parser and then at runtime, unpacked and loaded dynamically.

For instance, the following commands:

JSON_BASH_PARSER=$(cat <<EOF
cigpIHsgaWYgWyAtdCAwIF07IHRoZW4gYj0kKHN0dHkgLWcpOyBzdHR5IC1pY2Fub24gLWVjaG8g
bWluIDEgdGltZSAwOyBmaTsgYz0kKGRkIGJzPTEgY291bnQ9MSBjb252PW5vZXJyb3Isc3luYyAy
Pi9kZXYvbnVsbCk7IGlmIFsgLXQgMCBdOyB0aGVuIHN0dHkgIiRiIjsgZmk7IH07IGUoKSB7IGVj
aG8gIiQxPSQyIjsgfTsgYSgpIHsgbG9jYWwgZD0iJHsxOiskMS59JDIiOyBsb2NhbCBmPSJ1Ijsg
bG9jYWwgZz0wOyB3aGlsZSBbICIkaCIgLWx0ICIkaiIgXTsgZG8gWyAiJGkiID0gIjAiIF0gJiYg
aD0kKChoICsgMSkpICYmIHIgYzsgaT0wOyBjPSR7YzotJyAnfTsgY2FzZSAiJGYiIGluICJ1Iikg
Y2FzZSAiJGMiIGluICd7JykgbyAiJGQiICIkZyI7IGY9ImwiOyA7OyAnXScpIHJldHVybjsgOzsg
W1widGZURlwtMC05XSkgaT0xOyB2ICIkZCIgIiRnIjsgaT0xOyBmPSJsIjsgOzsgZXNhYyA7OyAi
bCIpIFsgIiRjIiA9ICIsIiBdICYmIGc9JCgoZyArIDEpKSAmJiBmPSJ1IjsgWyAiJGMiID0gIl0i
IF0gJiYgcmV0dXJuOyA7OyBlc2FjIGRvbmUgfTsgdigpIHsgbG9jYWwgZD0iJHsxOiskMS59JDIi
OyBsb2NhbCBmPSJ1Ijsgd2hpbGUgWyAiJGgiIC1sdCAiJGoiIF07IGRvIFsgIiRpIiA9ICIwIiBd
ICYmIGg9JCgoaCArIDEpKSAmJiByIGM7IGk9MDsgYz0ke2M6LScgJ307IGNhc2UgIiRmIiBpbiAi
dSIpIGNhc2UgIiRjIiBpbiAnIicpIGY9InoiOyBrPSIiOyA7OyBbXC0wLTldKSBmPSJ5Ijsgaz0i
JGMiOyA7OyBbdGZURl0pIGY9IngiOyBrPSIkYyI7IDs7ICJbIikgYSAiIiAiJGQiOyByZXR1cm47
IDs7ICJ7IikgbyAiIiAiJGQiOyByZXR1cm47IDs7IGVzYWMgOzsgInoiKSBjYXNlICIkYyIgaW4g
JyInKSBbICIkbSIgPSAiMCIgXSAmJiBlICIkZCIgIiRrIiAmJiByZXR1cm47IFsgIiRtIiA9ICIx
IiBdICYmIGs9IiRrJGMiICYmIG09MDsgOzsgJ1wnKSBbICIkbSIgPSAiMSIgXSAmJiBrPSIkayRj
IjsgbT0kKCgxIC0gbSkpOyA7OyAqKSBtPTA7IGs9IiRrJGMiOyA7OyBlc2FjIDs7ICJ5IikgY2Fz
ZSAiJGMiIGluIFssXF19XSkgZSAiJGQiICIkayI7IGk9MTsgcmV0dXJuIDs7IFtcLTAtOS5dKSBr
PSIkayRjIjsgOzsgZXNhYyA7OyAieCIpIGNhc2UgIiRjIiBpbiBbLFxdfV0pIGUgIiRkIiAiJGsi
OyBpPTE7IHJldHVybjsgOzsgW2EtekEtWl0pIGs9IiRrJGMiOyA7OyBlc2FjIDs7IGVzYWMgZG9u
ZSB9OyBvKCkgeyBsb2NhbCBkPSIkezE6KyQxLn0kMiI7IGxvY2FsIGY9InUiOyB3aGlsZSBbICIk
aCIgLWx0ICIkaiIgXTsgZG8gWyAiJGkiID0gIjAiIF0gJiYgaD0kKChoICsgMSkpICYmIHIgYzsg
aT0wOyBjPSR7YzotJyAnfTsgY2FzZSAiJGYiIGluICJ1IikgWyAiJGMiID0gIn0iIF0gJiYgcmV0
dXJuOyBbICIkYyIgPSAiXCIiIF0gJiYgZj0idyIgJiYgbj0iIiAmJiBtPTA7IDs7ICJ3IikgY2Fz
ZSAiJGMiIGluICciJykgWyAiJG0iID0gIjAiIF0gJiYgZj0icCI7IFsgIiRtIiA9ICIxIiBdICYm
IG49IiRuJGMiICYmIG09MDsgOzsgJ1wnKSBtPSQoKDEgLSBtKSk7IG49IiRuJGMiOyA7OyAqKSBt
PTA7IG49IiRuJGMiOyA7OyBlc2FjIDs7ICJwIikgWyAiJGMiID0gIjoiIF0gJiYgdiAiJGQiICIk
biIgJiYgZj0icSI7IDs7ICJxIikgWyAiJGMiID0gJywnIF0gJiYgZj0idSI7IFsgIiRjIiA9ICd9
JyBdICYmIHJldHVybjsgOzsgZXNhYyBkb25lIH07IHMoKSB7IGg9MDsgaT0wOyB3aGlsZSBbICIk
aCIgLWx0ICIkaiIgXTsgZG8gciBjOyBjPSR7YzotJyAnfTsgaD0kKChoICsgMSkpOyBbICIkYyIg
PSAieyIgXSAmJiBvICIiICIiICYmIHJldHVybjsgWyAiJGMiID0gIlsiIF0gJiYgYSAiIiAiIiAm
JiByZXR1cm47IGRvbmUgfTsganNvbigpIHsgaWYgWyAteiAiJEAiIF07IHRoZW4gdD0kKGNhdCAt
KTsgZWxzZSB0PSQoZWNobyAiJEAiKTsgZmk7IGo9IiR7I3R9IjsgZWNobyAiJHt0fSIgfCBzOyB9
Cgo=
EOF
)
 
eval `printf '%s\n' "$JSON_BASH_PARSER" | base64 -d`

will unpack the JSON parser by base64 decoding the string and then load the defined functions using eval. After the previous snippet, an instruction such as:

echo '{ "a": "b", "c": [ "hallo", "welt" ] }' | json

will print out the expected output from the original script.

Practical Applications


sh/json_parser.txt ยท Last modified: 2022/04/19 08:28 by 127.0.0.1

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.