#!/bin/sh proc throw { echo "$ifsjoin(Argv)" > !2 exit 1 } setglobal BRIEF = '0' setglobal LEAFONLY = '0' setglobal PRUNE = '0' setglobal NO_HEAD = '0' setglobal NORMALIZE_SOLIDUS = '0' proc usage { echo echo "Usage: JSON.sh [-b] [-l] [-p] [-s] [-h]" echo echo "-p - Prune empty. Exclude fields with empty values." echo "-l - Leaf only. Only show leaf nodes, which stops data duplication." echo "-b - Brief. Combines 'Leaf only' and 'Prune empty' options." echo "-n - No-head. Do not show nodes that have no path (lines that start with [])." echo "-s - Remove escaping of the solidus symbol (straight slash)." echo "-h - This help text." echo } proc parse_options { set -- @Argv local ARGN=$Argc while [ "$ARGN" -ne 0 ] { match $1 { with -h usage exit 0 with -b setglobal BRIEF = '1' setglobal LEAFONLY = '1' setglobal PRUNE = '1' with -l setglobal LEAFONLY = '1' with -p setglobal PRUNE = '1' with -n setglobal NO_HEAD = '1' with -s setglobal NORMALIZE_SOLIDUS = '1' with ?* echo "ERROR: Unknown option." usage exit 0 } shift 1 setglobal ARGN = $shExpr('ARGN-1') } } proc awk_egrep { local pattern_string=$1 gawk '{ while ($0) { start=match($0, pattern); token=substr($0, start, RLENGTH); print token; $0=substr($0, start+RLENGTH); } }' pattern="$pattern_string" } proc tokenize { local GREP local ESCAPE local CHAR if echo "test string" | egrep -ao --color=never "test" >/dev/null !2 > !1 { setglobal GREP = ''egrep -ao --color=never'' } else { setglobal GREP = ''egrep -ao'' } if echo "test string" | egrep -o "test" >/dev/null !2 > !1 { setglobal ESCAPE = ''(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'' setglobal CHAR = ''[^[:cntrl:]"\\]'' } else { setglobal GREP = 'awk_egrep' setglobal ESCAPE = ''(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'' setglobal CHAR = ''[^[:cntrl:]"\\\\]'' } local STRING="\"$CHAR*($ESCAPE$CHAR*)*\"" local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' local KEYWORD='null|false|true' local SPACE='[[:space:]]+' # Force zsh to expand $A into multiple words local is_wordsplit_disabled=$[unsetopt !2 >/dev/null | grep -c '^shwordsplit$] if test $is_wordsplit_disabled != 0 { setopt shwordsplit; } $GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$" if test $is_wordsplit_disabled != 0 { unsetopt shwordsplit; } } proc parse_array { local index=0 local ary='' read -r token match $token { with ']' with * while : { parse_value $1 $index setglobal index = $shExpr('index+1') setglobal ary = ""$ary""$value"" read -r token match $token { with ']' break with ',' setglobal ary = ""$ary,"" with * throw "EXPECTED , or ] GOT $(token:-EOF)" } read -r token } } test $BRIEF -eq 0 && setglobal value = $[printf '[%s]' $ary] || setglobal value = '' : } proc parse_object { local key local obj='' read -r token match $token { with '}' with * while : { match $token { with '"'*'"' setglobal key = $token with * throw "EXPECTED string GOT $(token:-EOF)" } read -r token match $token { with ':' with * throw "EXPECTED : GOT $(token:-EOF)" } read -r token parse_value $1 $key setglobal obj = ""$obj$key:$value"" read -r token match $token { with '}' break with ',' setglobal obj = ""$obj,"" with * throw "EXPECTED , or } GOT $(token:-EOF)" } read -r token } } test $BRIEF -eq 0 && setglobal value = $[printf '{%s}' $obj] || setglobal value = '' : } proc parse_value { local jpath="$(1:+$1,)$2" isleaf=0 isempty=0 print=0 match $token { with '{' parse_object $jpath with '[' parse_array $jpath # At this point, the only valid single-character tokens are digits. with ''|[!0-9] throw "EXPECTED value GOT $(token:-EOF)" with * setglobal value = $token # if asked, replace solidus ("\/") in json strings with normalized value: "/" test $NORMALIZE_SOLIDUS -eq 1 && setglobal value = $[echo $value | sed 's#\\/#/#g] setglobal isleaf = '1' test $value = '""' && setglobal isempty = '1' } test $value = '' && return test $NO_HEAD -eq 1 && test -z $jpath && return test $LEAFONLY -eq 0 && test $PRUNE -eq 0 && setglobal print = '1' test $LEAFONLY -eq 1 && test $isleaf -eq 1 && test $PRUNE -eq 0 && setglobal print = '1' test $LEAFONLY -eq 0 && test $PRUNE -eq 1 && test $isempty -eq 0 && setglobal print = '1' test $LEAFONLY -eq 1 && test $isleaf -eq 1 && \ test $PRUNE -eq 1 && test $isempty -eq 0 && setglobal print = '1' test $print -eq 1 && printf "[%s]\t%s\n" $jpath $value : } proc parse { read -r token parse_value read -r token match $token { with '' with * throw "EXPECTED EOF GOT $token" } } if shell {test $0 = $BASH_SOURCE || ! test -n $BASH_SOURCE} { parse_options @Argv tokenize | parse } # vi: expandtab sw=2 ts=2