#!/bin/bash # # Synopsis: # # #!/bin/bash # . export-json.bash # export_JSON # # Example: # # $ ( # source export-json.bash # export_JSON SSH_TTY \ # SSH_CONNECTION \ # SSH_CLIENT \ # SSH_AUTH_SOCK # ) # # { # "SSH_TTY": "/dev/pts/0", # "SSH_CONNECTION": "192.0.2.140 41564 198.51.100.25 22", # "SSH_CLIENT": "192.0.2.140 41564 22", # "SSH_AUTH_SOCK": "/tmp/ssh-XXXXcePKJl/agent.36326" # } # # This bash function exports shell variables as # JSON ("javascript object notation") strings. # # The string is printed to stdout. # # JSON is a format that represents data as # Javascript source code so modern programmers # can read it unlike bash source code. # # Variable names are given to the function as its # argument list: # # $ (source export-json.bash; export_JSON PATH) # # The output is a single JSON object containing # key-value mappings between JSON strings: # # { # "PATH": "/usr/local/sbin:[...]" # } # # It uses the external tool "jq" to parse string # values placed in jq's argument list by bash and # then encode them as JSON string values. This # is no accidental dependency. The jq program is # the foundation of the trustworthiness of this # code. If we were encoding JSON strings in bash # we would have to be a lot more careful. arg1_to_env0() { case "$1" in *[^a-zA-Z0-9_=]* | *=*=* ) echo "Error: invalid variable: '$1'" >&2 return 10 ;; [a-zA-Z_]* ) set -- "${1#*=}" "${1%%=*}" ;; * ) echo "Error: invalid variable: '$1'" >&2 return 20 ;; esac if [ -v "$2" ] then printf '%s=%s\0' "$1" "${!2}" else echo "Warning: ignoring unset variable: '$2'" >&2 fi } vars_to_env0() { while [ $# -gt 0 ] do arg1_to_env0 "$1" || return shift done } env0_to_JSON() { set -- while read -d '' do set -- "$@" --arg "${REPLY%%=*}" "${REPLY#*=}" done jq -n -r '$ARGS.named' "$@" } export_JSON_unsafe() { ( set -o pipefail vars_to_env0 "$@" | env0_to_JSON ) } safe_stdout() { set -- "$(mktemp)" "$@" if (shift; "$@") > "$1" then r=0 cat < "$1" else r=$? fi rm "$1" return $r } export_JSON() { safe_stdout export_JSON_unsafe "$@" } try() { "$@" printf '(Exit %s) <- [%s]\n' "$?" "${*@Q}" >&2 } runtest() { set -- SSH_CLIENT SSH_TTY SSH_AUTH_SOCK SSH_CONNECTION try export_JSON "$@" unset unsetvar try export_JSON SSH_TTY unsetvar try export_JSON unsetvar SSH_TTY try export_JSON try export_JSON '' try export_JSON '' SSH_TTY }