JSON.sh 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #!/bin/bash
  2. throw() {
  3. echo "$*" >&2
  4. exit 1
  5. }
  6. BRIEF=0
  7. LEAFONLY=0
  8. PRUNE=0
  9. NO_HEAD=0
  10. NORMALIZE_SOLIDUS=0
  11. usage() {
  12. echo
  13. echo "Usage: JSON.sh [-b] [-l] [-p] [-s] [-h]"
  14. echo
  15. echo "-p - Prune empty. Exclude fields with empty values."
  16. echo "-l - Leaf only. Only show leaf nodes, which stops data duplication."
  17. echo "-b - Brief. Combines 'Leaf only' and 'Prune empty' options."
  18. echo "-c - Brief. Combines 'Leaf only' and 'Prune empty' options, but print code"
  19. echo "-n - No-head. Do not show nodes that have no path (lines that start with [])."
  20. echo "-s - Remove escaping of the solidus symbol (straight slash)."
  21. echo "-h - This help text."
  22. echo
  23. }
  24. parse_options() {
  25. set -- "$@"
  26. local ARGN=$#
  27. while [ "$ARGN" -ne 0 ]
  28. do
  29. case $1 in
  30. -h) usage
  31. exit 0
  32. ;;
  33. -b) BRIEF=1
  34. LEAFONLY=1
  35. PRUNE=1
  36. CODE=0
  37. ;;
  38. -c) BRIEF=1
  39. LEAFONLY=1
  40. PRUNE=1
  41. CODE=1
  42. ;;
  43. -l) LEAFONLY=1
  44. ;;
  45. -p) PRUNE=1
  46. ;;
  47. -n) NO_HEAD=1
  48. ;;
  49. -s) NORMALIZE_SOLIDUS=1
  50. ;;
  51. ?*) echo "ERROR: Unknown option."
  52. usage
  53. exit 0
  54. ;;
  55. esac
  56. shift 1
  57. ARGN=$((ARGN-1))
  58. done
  59. }
  60. awk_egrep () {
  61. local pattern_string=$1
  62. gawk '{
  63. while ($0) {
  64. start=match($0, pattern);
  65. token=substr($0, start, RLENGTH);
  66. print token;
  67. $0=substr($0, start+RLENGTH);
  68. }
  69. }' pattern="$pattern_string"
  70. }
  71. tokenize () {
  72. local GREP
  73. local ESCAPE
  74. local CHAR
  75. if echo "test string" | egrep -ao --color=never "test" >/dev/null 2>&1
  76. then
  77. GREP='egrep -ao --color=never'
  78. else
  79. GREP='egrep -ao'
  80. fi
  81. if echo "test string" | egrep -o "test" >/dev/null 2>&1
  82. then
  83. ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
  84. CHAR='[^[:cntrl:]"\\]'
  85. else
  86. GREP=awk_egrep
  87. ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
  88. CHAR='[^[:cntrl:]"\\\\]'
  89. fi
  90. local STRING="\"$CHAR*($ESCAPE$CHAR*)*\""
  91. local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?'
  92. local KEYWORD='null|false|true'
  93. local SPACE='[[:space:]]+'
  94. # Force zsh to expand $A into multiple words
  95. local is_wordsplit_disabled=$(unsetopt 2>/dev/null | grep -c '^shwordsplit$')
  96. if [ $is_wordsplit_disabled != 0 ]; then setopt shwordsplit; fi
  97. $GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$"
  98. if [ $is_wordsplit_disabled != 0 ]; then unsetopt shwordsplit; fi
  99. }
  100. parse_array () {
  101. local index=0
  102. local ary=''
  103. read -r token
  104. case "$token" in
  105. ']') ;;
  106. *)
  107. while :
  108. do
  109. parse_value "$1" "$index"
  110. index=$((index+1))
  111. ary="$ary""$value"
  112. read -r token
  113. case "$token" in
  114. ']') break ;;
  115. ',') ary="$ary," ;;
  116. *) throw "EXPECTED , or ] GOT ${token:-EOF}" ;;
  117. esac
  118. read -r token
  119. done
  120. ;;
  121. esac
  122. [ "$BRIEF" -eq 0 ] && value=$(printf '[%s]' "$ary") || value=
  123. :
  124. }
  125. parse_object () {
  126. local key
  127. local obj=''
  128. read -r token
  129. case "$token" in
  130. '}') ;;
  131. *)
  132. while :
  133. do
  134. case "$token" in
  135. '"'*'"') key=$token ;;
  136. *) throw "EXPECTED string GOT ${token:-EOF}" ;;
  137. esac
  138. read -r token
  139. case "$token" in
  140. ':') ;;
  141. *) throw "EXPECTED : GOT ${token:-EOF}" ;;
  142. esac
  143. read -r token
  144. parse_value "$1" "$key"
  145. obj="$obj$key:$value"
  146. read -r token
  147. case "$token" in
  148. '}') break ;;
  149. ',') obj="$obj," ;;
  150. *) throw "EXPECTED , or } GOT ${token:-EOF}" ;;
  151. esac
  152. read -r token
  153. done
  154. ;;
  155. esac
  156. [ "$BRIEF" -eq 0 ] && value=$(printf '{%s}' "$obj") || value=
  157. :
  158. }
  159. parse_value () {
  160. local jpath="${1:+$1,}$2" isleaf=0 isempty=0 print=0
  161. local cpath="$(echo ${jpath}|sed 's:"*,"*:__:g;s:"::g')"
  162. case "$token" in
  163. '{') parse_object "$jpath" ;;
  164. '[') parse_array "$jpath" ;;
  165. # At this point, the only valid single-character tokens are digits.
  166. ''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;;
  167. *) value=$token
  168. # if asked, replace solidus ("\/") in json strings with normalized value: "/"
  169. [ "$NORMALIZE_SOLIDUS" -eq 1 ] && value=$(echo "$value" | sed 's#\\/#/#g')
  170. isleaf=1
  171. [ "$value" = '""' ] && isempty=1
  172. ;;
  173. esac
  174. [ "$value" = '' ] && return
  175. [ "$NO_HEAD" -eq 1 ] && [ -z "$jpath" ] && return
  176. [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 0 ] && print=1
  177. [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && [ $PRUNE -eq 0 ] && print=1
  178. [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 1 ] && [ "$isempty" -eq 0 ] && print=1
  179. [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && \
  180. [ $PRUNE -eq 1 ] && [ $isempty -eq 0 ] && print=1
  181. if [ "$CODE" = "0" ]; then
  182. [ "$print" -eq 1 ] && printf "[%s]\t%s\n" "$jpath" "$value"
  183. else
  184. [ "$print" -eq 1 ] && printf "%s=%s\n" "$cpath" "$value"
  185. fi
  186. :
  187. }
  188. parse () {
  189. read -r token
  190. parse_value
  191. read -r token
  192. case "$token" in
  193. '') ;;
  194. *) throw "EXPECTED EOF GOT $token" ;;
  195. esac
  196. }
  197. if ([ "$0" = "$BASH_SOURCE" ] || ! [ -n "$BASH_SOURCE" ]);
  198. then
  199. parse_options "$@"
  200. tokenize | parse
  201. fi
  202. # vi: expandtab sw=2 ts=2