|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +## Bash library for pretty-printing a variable given by name. |
| 4 | +# |
| 5 | +# This was inspired by `util.inspect` in Node.js, `p` in or `pp` in ruby, |
| 6 | +# `var_dump` in php, etc. This is intended for developers to use for debugging |
| 7 | +# - this should not be parsed by a machine nor should the output format be |
| 8 | +# considered stable. |
| 9 | +# |
| 10 | +# Author: Dave Eddy <[email protected]> |
| 11 | +# Date: December 18, 2024 |
| 12 | +# License: MIT |
| 13 | + |
| 14 | +# vardump |
| 15 | +# |
| 16 | +# Dump the given variable (by name) information and value to stdout. |
| 17 | +# |
| 18 | +# Usage: vardump [-v] <name> |
| 19 | +# |
| 20 | +# Example: |
| 21 | +# |
| 22 | +# ``` bash |
| 23 | +# $ declare -A assoc=([foo]=1 [bar]=2) |
| 24 | +# $ vardump assoc |
| 25 | +# ( |
| 26 | +# ['foo']='1' |
| 27 | +# ['bar']='2' |
| 28 | +# ) |
| 29 | +# $ vardump -v assoc |
| 30 | +# -------------------------- |
| 31 | +# vardump: assoc |
| 32 | +# attributes: (A)associative array |
| 33 | +# length: 2 |
| 34 | +# ( |
| 35 | +# ['foo']='1' |
| 36 | +# ['bar']='2' |
| 37 | +# ) |
| 38 | +# -------------------------- |
| 39 | +# ``` |
| 40 | +# |
| 41 | +# Arguments: |
| 42 | +# -v verbose output |
| 43 | +# -C [always|auto|never] when to colorize output, defaults to auto |
| 44 | +# |
| 45 | +vardump() { |
| 46 | + # read arguments |
| 47 | + local verbose=false |
| 48 | + local whencolor='auto' |
| 49 | + local OPTIND OPTARG opt |
| 50 | + while getopts 'C:v' opt; do |
| 51 | + case "$opt" in |
| 52 | + C) whencolor=$OPTARG;; |
| 53 | + v) verbose=true;; |
| 54 | + *) return 1;; |
| 55 | + esac |
| 56 | + done |
| 57 | + shift "$((OPTIND - 1))" |
| 58 | + |
| 59 | + # read variable name |
| 60 | + local name=$1 |
| 61 | + |
| 62 | + if [[ -z $name ]]; then |
| 63 | + echo 'vardump: name required as first argument' >&2 |
| 64 | + return 1 |
| 65 | + fi |
| 66 | + |
| 67 | + # optionally load colors |
| 68 | + if [[ $whencolor == always ]] || [[ $whencolor == auto && -t 1 ]]; then |
| 69 | + local color_green=$'\e[32m' |
| 70 | + local color_magenta=$'\e[35m' |
| 71 | + local color_rst=$'\e[0m' |
| 72 | + local color_dim=$'\e[2m' |
| 73 | + else |
| 74 | + local color_green='' |
| 75 | + local color_magenta='' |
| 76 | + local color_rst='' |
| 77 | + local color_dim='' |
| 78 | + fi |
| 79 | + local color_value=$color_green |
| 80 | + local color_key=$color_magenta |
| 81 | + local color_length=$color_magenta |
| 82 | + |
| 83 | + # optionally print header |
| 84 | + if $verbose; then |
| 85 | + echo "${color_dim}--------------------------${color_rst}" |
| 86 | + echo "${color_dim}vardump: ${color_rst}$name" |
| 87 | + fi |
| 88 | + |
| 89 | + # ensure the variable is defined |
| 90 | + if ! declare -p "$name" &>/dev/null; then |
| 91 | + echo "variable ${name@Q} not defined" >&2 |
| 92 | + return 1 |
| 93 | + fi |
| 94 | + |
| 95 | + # get the variable attributes - this will tell us what kind of variable |
| 96 | + # it is. |
| 97 | + # |
| 98 | + # XXX dave says - is this ideal? when the variable is a nameref (using |
| 99 | + # `declare -n` or `local -n`) it seems like this method follows the |
| 100 | + # nameref, whereas parsing the output of `declare -p <name>` seems to |
| 101 | + # correctly give `-n` as the set of arguments used. Perhaps just parse |
| 102 | + # the output of `declare -p` from above here instead? |
| 103 | + local attrs |
| 104 | + IFS='' read -ra attrs <<< "${!name@a}" |
| 105 | + |
| 106 | + # parse the variable attributes and construct a human-readable string |
| 107 | + local attributes=() |
| 108 | + local attr |
| 109 | + local typ='' |
| 110 | + for attr in "${attrs[@]}"; do |
| 111 | + local s='' |
| 112 | + case "$attr" in |
| 113 | + a) s='indexed array'; typ='a';; |
| 114 | + A) s='associative array'; typ='A';; |
| 115 | + r) s='read-only';; |
| 116 | + i) s='integer';; |
| 117 | + g) s='global';; |
| 118 | + x) s='exported';; |
| 119 | + *) s="unknown";; |
| 120 | + esac |
| 121 | + attributes+=("($attr)$s") |
| 122 | + done |
| 123 | + |
| 124 | + # optionally print the attributes to the user |
| 125 | + if $verbose; then |
| 126 | + echo -n "${color_dim}attributes: ${color_rst}" |
| 127 | + if [[ -n ${attributes[0]} ]]; then |
| 128 | + # separate the list of attributes by a `/` character |
| 129 | + ( |
| 130 | + IFS=/ |
| 131 | + echo -n "${attributes[*]}" |
| 132 | + ) |
| 133 | + else |
| 134 | + echo -n '(none)' |
| 135 | + fi |
| 136 | + echo |
| 137 | + fi |
| 138 | + |
| 139 | + # print the variable itself! we use $typ defined above to format it |
| 140 | + # appropriately. |
| 141 | + # |
| 142 | + # we *pray* the user doesn't use this variable name in their own code or |
| 143 | + # we'll hit a circular reference error (THAT WE CAN'T CATCH OURSELVES IN |
| 144 | + # CODE - lame) |
| 145 | + local -n __vardump_name="$name" |
| 146 | + |
| 147 | + if [[ $typ == 'a' || $typ == 'A' ]]; then |
| 148 | + # print this as an array - indexed, sparse, associative, |
| 149 | + # whatever |
| 150 | + local key value |
| 151 | + |
| 152 | + # optionally print length |
| 153 | + if $verbose; then |
| 154 | + local length=${#__vardump_name[@]} |
| 155 | + printf '%s %s\n' \ |
| 156 | + "${color_dim}length:${color_rst}" \ |
| 157 | + "${color_length}$length${color_rst}" |
| 158 | + fi |
| 159 | + |
| 160 | + # loop keys and print the data itself |
| 161 | + echo '(' |
| 162 | + for key in "${!__vardump_name[@]}"; do |
| 163 | + value=${__vardump_name[$key]} |
| 164 | + |
| 165 | + # safely quote the key name if it's an associative array |
| 166 | + # (the user controls the key names in this case so we |
| 167 | + # can't trust them to be safe) |
| 168 | + if [[ $typ == 'A' ]]; then |
| 169 | + key=${key@Q} |
| 170 | + fi |
| 171 | + |
| 172 | + # always safely quote the value |
| 173 | + value=${value@Q} |
| 174 | + |
| 175 | + printf '\t[%s]=%s\n' \ |
| 176 | + "${color_key}$key${color_rst}" \ |
| 177 | + "${color_value}$value${color_rst}" |
| 178 | + done |
| 179 | + echo ')' |
| 180 | + else |
| 181 | + # we are just a simple scalar value - print this as a regular, |
| 182 | + # safely-quoted, value. |
| 183 | + echo "${color_value}${__vardump_name@Q}${color_rst}" |
| 184 | + fi |
| 185 | + |
| 186 | + # optionally print the trailer |
| 187 | + if $verbose; then |
| 188 | + echo "${color_dim}--------------------------${color_rst}" |
| 189 | + fi |
| 190 | + |
| 191 | + return 0 |
| 192 | + |
| 193 | +} |
| 194 | + |
| 195 | +# if we are run directly (not-sourced) then run through some examples |
| 196 | +# shellcheck disable=SC2034 |
| 197 | +if ! (return &>/dev/null); then |
| 198 | + declare s='some string' |
| 199 | + declare -r READ_ONLY='this cant be changed' |
| 200 | + declare -i int_value=5 |
| 201 | + |
| 202 | + declare -a simple_array=(foo bar baz "$(tput setaf 1)red$(tput sgr0)") |
| 203 | + declare -a sparse_array=([0]=hi [5]=bye [7]=ok) |
| 204 | + declare -A assoc_array=([foo]=1 [bar]=2) |
| 205 | + |
| 206 | + echo 'simple vardump' |
| 207 | + for var in s READ_ONLY int_value simple_array sparse_array assoc_array; do |
| 208 | + vardump "$var" |
| 209 | + done |
| 210 | + |
| 211 | + echo 'verbose vardump' |
| 212 | + for var in s READ_ONLY int_value simple_array sparse_array assoc_array; do |
| 213 | + vardump -v "$var" |
| 214 | + echo |
| 215 | + done |
| 216 | +fi |
0 commit comments