Last active
July 27, 2026 09:13
-
-
Save Lohann/9ea5c2b4c2c224e17950d87ed1ab9459 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # locals_snapshot | |
| # ---------------------- | |
| # An over-engineered Sed script that parses the output of 'set' builtin and format | |
| # one variable per line (even when it's value contains multiple lines), to handle | |
| # newlines and raw bytes, all values are formated in C printf format: | |
| # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap05.html#tag_05 | |
| # | |
| # # Why? | |
| # The command `set` display all local variables in the shell, but unfortunatelly its | |
| # output isn't standartized between different shells, so there's no portable solution | |
| # to extract it's output, even check the variable existence is not trivial once multi | |
| # line values may cause false positives, ex: v='foo=value<NEW LINE>bar=value'. The sed | |
| # script below is a minimal shell script parser, it unquotes strings, parses escaped | |
| # characters, extracting the variable's original value, then convert multi-line values | |
| # to single line printf-like format. | |
| # | |
| # # USAGE | |
| # Example 01: Compare variables before and after executing some code. | |
| # ``` | |
| # before=`take_snapshot` | |
| # <alter some variables> | |
| # after=`take_snapshot` | |
| # printf '%s\n\n' "$before" "$after" | |
| # ``` | |
| # | |
| # Example 02: Filter by variable name. | |
| # ``` | |
| # snapshot=`take_snapshot | grep '^VARNAME'` | |
| # ``` | |
| # | |
| # Example 03: List varname and value (obs: loses original value trailing spaces) | |
| # ``` | |
| # snapshot=`take_snapshot` | |
| # varname= value= ifs_backup=$IFS IFS=' | |
| # ' | |
| # for line in $snapshot; do | |
| # varname=`expr "X$line" : 'X\([^=]*\)=.*'` | |
| # if value=`expr "X$line" : '[^=]*=\(.*\)'` | |
| # then eval "value=\`printf $value\`" | |
| # else value= | |
| # fi | |
| # printf '%s\n' "varname=$varname, value=$value" | |
| # done | |
| # IFS=$ifs_backup | |
| # ``` | |
| # | |
| # Example 04: Parse original value (keeps trailing spaces) | |
| # ``` | |
| # snapshot=`take_snapshot` | |
| # varname= quoted= value= line= ifs_backup=$IFS IFS=' | |
| # ' | |
| # for line in $snapshot | |
| # do | |
| # IFS=$ifs_backup varname= quoted= value= | |
| # varname=`expr "X$line" : 'X\([^=]*\)=.*'` || n= | |
| # if quoted=`expr "X$line" : '[^=]*=\(.*\)'`; then | |
| # # 1 - Use printf to extract the original value | |
| # # 2 - Use sed to quote the original value (otherwise trailing spaces are automatically removed) | |
| # # 3 - Use eval to assign the quoted value to a variable. | |
| # value="printf x${quoted}x | LC_ALL=C sed -e \"s/'/'\\\\\\\\''/g\" -e \"1s/^x/'/\" -e \"\\\$s/x\\\$/'/\"" | |
| # value=`eval "$value"` && eval "value=$value" || value= | |
| # else quoted= value= | |
| # fi | |
| # printf '%s\n' "varname=$varname, quoted=$quoted, value=$value" | |
| # done | |
| # IFS=$ifs_backup | |
| # ``` | |
| # | |
| # Note: Doesn't parse bash's 'Indexed Array' and 'Associative Array'. | |
| # @author Lohann Ferreira <developer@lohann.dev> | |
| take_snapshot() | |
| { | |
| # save Sed Script as positional argument (avoiding overwrite global variables) | |
| # | |
| # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) | |
| set x '{ | |
| /^[[:space:]]*#/b | |
| s/^[^=]*$/&=/ | |
| /^[_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ][0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]*=/!b | |
| /^[^=]*=(/{ | |
| :skip | |
| /)[[:space:]]*$/!{ | |
| $!b fail | |
| N | |
| b skip | |
| } | |
| d | |
| b | |
| } | |
| :clear1 | |
| t clear1 | |
| s/^\([^=]*=\)[[:space:]]*$/\1/ | |
| t end | |
| /^[^=]*=/{ | |
| h | |
| s/^\([^=]*=\).*$/\1/ | |
| x | |
| s/^[^=]*=// | |
| b parse | |
| } | |
| b fail | |
| :parse | |
| t parse | |
| /^$/b end | |
| /^[[:space:]]/b fail | |
| s/^'\''// | |
| t quote | |
| s/^"// | |
| t dquote | |
| s/^\$'\''// | |
| t ansi | |
| s/^\\'\''/'\''\nq'\''/ | |
| t plain | |
| s/^\\\(.\)/'\''\1'\''/ | |
| t plain | |
| s|^[/0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:=.,%@?!+-]\{1,\}|'\''&'\''| | |
| t plain | |
| b fail | |
| :plain | |
| H | |
| s/^'\''[^'\'']*'\''// | |
| x | |
| s/^\([^'\'']*\)[^'\'']'\''\([^'\'']*\)'\''.*$/\1\2/ | |
| x | |
| b parse | |
| :ansi | |
| t ansi | |
| s/^'\''// | |
| t parse | |
| s/^[^'\''\\]\{1,\}/'\''&'\''/ | |
| t next | |
| s/^\\// | |
| t clear | |
| b fail | |
| :clear | |
| s/^'\''/'\''\nq'\''/ | |
| t next | |
| s/^[\\abeEtnvfr]/'\''\n&'\''/ | |
| t next | |
| s/^x[0123456789ABCDEFabcdef][0123456789ABCDEFabcdef]/'\''\n&'\''/ | |
| s/^x\([0123456789ABCDEFabcdef]\)/'\''\nx0\1'\''/ | |
| t next | |
| s/^U[0123456789ABCDEFabcdef]\{1,8\}/'\''\n&U'\''/ | |
| t next | |
| s/^u[0123456789ABCDEFabcdef]\{1,4\}/'\''\n&u'\''/ | |
| t next | |
| s/^0[01234567][01234567][01234567]/'\''\no&o'\''/ | |
| s/^[01234567][01234567][01234567]/'\''\no&o'\''/ | |
| s/^[01234567][01234567]/'\''\no0&o'\''/ | |
| s/^[01234567]/'\''\no00&o'\''/ | |
| t next | |
| s/^C-// | |
| t ctrl | |
| s/^C// | |
| t ctrl | |
| s/^M-// | |
| t mctrl | |
| s/^M// | |
| t mctrl | |
| b fail | |
| :mctrl | |
| t mctrl | |
| s/^@/'\''\no300o'\''/ | |
| s/^A/'\''\no301o'\''/ | |
| s/^B/'\''\no302o'\''/ | |
| s/^C/'\''\no303o'\''/ | |
| s/^D/'\''\no304o'\''/ | |
| s/^E/'\''\no305o'\''/ | |
| s/^F/'\''\no306o'\''/ | |
| s/^G/'\''\no307o'\''/ | |
| s/^H/'\''\no310o'\''/ | |
| s/^I/'\''\no311o'\''/ | |
| s/^J/'\''\no312o'\''/ | |
| s/^K/'\''\no313o'\''/ | |
| s/^L/'\''\no314o'\''/ | |
| s/^M/'\''\no315o'\''/ | |
| s/^N/'\''\no316o'\''/ | |
| s/^O/'\''\no317o'\''/ | |
| s/^P/'\''\no320o'\''/ | |
| s/^Q/'\''\no321o'\''/ | |
| s/^R/'\''\no322o'\''/ | |
| s/^S/'\''\no323o'\''/ | |
| s/^T/'\''\no324o'\''/ | |
| s/^U/'\''\no325o'\''/ | |
| s/^V/'\''\no326o'\''/ | |
| s/^W/'\''\no327o'\''/ | |
| s/^X/'\''\no330o'\''/ | |
| s/^Y/'\''\no331o'\''/ | |
| s/^Z/'\''\no332o'\''/ | |
| s/^\[/'\''\n3033o'\''/ | |
| s/^\\/'\''\n3034o'\''/ | |
| s/^\]/\no335o'\''/ | |
| s/^\^/'\''\no336o'\''/ | |
| s/^_/'\''\no337o'\''/ | |
| s/^[?]/'\''\no377o'\''/ | |
| t next | |
| b fail | |
| :ctrl | |
| t ctrl | |
| s/^@/'\''\no000o'\''/ | |
| s/^A/'\''\no001o'\''/ | |
| s/^B/'\''\no002o'\''/ | |
| s/^C/'\''\no003o'\''/ | |
| s/^D/'\''\no004o'\''/ | |
| s/^E/'\''\no005o'\''/ | |
| s/^F/'\''\no006o'\''/ | |
| s/^G/'\''\na'\''/ | |
| s/^H/'\''\nb'\''/ | |
| s/^I/'\''\nt'\''/ | |
| s/^J/'\''\nn'\''/ | |
| s/^K/'\''\nv'\''/ | |
| s/^L/'\''\nf'\''/ | |
| s/^M/'\''\nr'\''/ | |
| s/^N/'\''\no016o'\''/ | |
| s/^O/'\''\no017o'\''/ | |
| s/^P/'\''\no020o'\''/ | |
| s/^Q/'\''\no021o'\''/ | |
| s/^R/'\''\no022o'\''/ | |
| s/^S/'\''\no023o'\''/ | |
| s/^T/'\''\no024o'\''/ | |
| s/^U/'\''\no025o'\''/ | |
| s/^V/'\''\no026o'\''/ | |
| s/^W/'\''\no027o'\''/ | |
| s/^X/'\''\no030o'\''/ | |
| s/^Y/'\''\no031o'\''/ | |
| s/^Z/'\''\no032o'\''/ | |
| s/^\[/'\''\ne'\''/ | |
| s/^[\\]/'\''\no034o'\''/ | |
| s/^\]'\''/\no035o'\''/ | |
| s/^\^/'\''\no036o'\''/ | |
| s/^_/'\''\no037o'\''/ | |
| s/^[?]/'\''\no177o'\''/ | |
| t next | |
| b fail | |
| :next | |
| t next | |
| /^'\''[^'\'']*'\''/!{ | |
| b fail | |
| } | |
| H | |
| s/^'\''[^'\'']*'\''// | |
| x | |
| s/^\([^'\'']*\)[^'\'']'\''\([^'\'']*\)'\''.*$/\1\2/ | |
| x | |
| b ansi | |
| :quote | |
| t quote | |
| /^$/{ | |
| $b fail | |
| x | |
| s/$/\nn/ | |
| x | |
| n | |
| b quote | |
| } | |
| s/^'\''// | |
| t parse | |
| s/^[^'\'']*'\''/'\''&/ | |
| t plain | |
| /^[^'\'']*[^'\'']/{ | |
| s/^\([^'\'']*[^'\'']\)/'\''&'\''/ | |
| H | |
| s/^'\''[^'\'']*'\''// | |
| x | |
| s/^\([^'\'']*\)[^'\'']'\''\([^'\'']*\)'\''.*$/\1\2/ | |
| x | |
| b quote | |
| } | |
| b fail | |
| :dquote | |
| t dquote | |
| s/^"// | |
| t parse | |
| s/^[^'\''"\\]\{1,\}/'\''&'\''/ | |
| t dnext | |
| s/^'\''/'\''\nq'\''/ | |
| t dnext | |
| s/^\\\([\\$"`]\)/'\''\1'\''/ | |
| t dnext | |
| /^\\$/{ | |
| s/^.*$// | |
| $b fail | |
| n | |
| b dquote | |
| } | |
| s/^\\/'\''&'\''/ | |
| t dnext | |
| /^$/{ | |
| $b fail | |
| x | |
| s/$/\nn/ | |
| x | |
| n | |
| b dquote | |
| } | |
| b fail | |
| :dnext | |
| /^'\''[^'\'']*[^'\'']'\''/!{ | |
| b fail | |
| } | |
| H | |
| s/'\''[^'\'']*'\''// | |
| x | |
| s/^\([^'\'']*\)[^'\'']'\''\([^'\'']*\)'\''.*$/\1\2/ | |
| x | |
| b dquote | |
| :fail | |
| s/\n/&n/g | |
| s/'\''/\nq/g | |
| s/^/'\''/ | |
| H | |
| s/^.*$// | |
| x | |
| s/^\([^'\'']*\)[^'\'']'\''\(.*\)$/\1\2/ | |
| s/\nq/\\'\''/g | |
| s/\n\([abtnvfr]\)/\\\1/g | |
| s/\no\([^o]*\)o/\\\1/g | |
| s/\n\(x..\)/\\\1/g | |
| s/\n\(u[^u]*\)u/\\\1/g | |
| s/\n\(U[^U]*\)U/\\\1/g | |
| s/^.*$/# invalid -> $'\''&'\''/ | |
| p | |
| q | |
| :end | |
| s/\n/&n/g | |
| s/'\''/\nq/g | |
| s/^/'\''/ | |
| H | |
| s/^.*$// | |
| x | |
| s/^\([^'\'']*\)[^'\'']'\''\(.*\)$/\1\2/ | |
| s/\\/\\&/g | |
| s/%/&%/g | |
| s/\n/'\''/g | |
| s/\x01/'\''o001o/g | |
| s/\x02/'\''o002o/g | |
| s/\x03/'\''o003o/g | |
| s/\x04/'\''o004o/g | |
| s/\x05/'\''o005o/g | |
| s/\x06/'\''o006o/g | |
| s/\x07/'\''o007o/g | |
| s/\x08/'\''o010o/g | |
| s/\x09/'\''t/g | |
| s/\x0b/'\''o013o/g | |
| s/\x0c/'\''o014o/g | |
| s/\x0d/'\''o015o/g | |
| s/\x0e/'\''o016o/g | |
| s/\x0f/'\''o017o/g | |
| s/\x10/'\''o020o/g | |
| s/\x11/'\''o021o/g | |
| s/\x12/'\''o022o/g | |
| s/\x13/'\''o023o/g | |
| s/\x14/'\''o024o/g | |
| s/\x15/'\''o025o/g | |
| s/\x16/'\''o026o/g | |
| s/\x17/'\''o027o/g | |
| s/\x18/'\''o030o/g | |
| s/\x19/'\''o031o/g | |
| s/\x1a/'\''o032o/g | |
| s/\x1b/'\''o033o/g | |
| s/\x1c/'\''o034o/g | |
| s/\x1d/'\''o035o/g | |
| s/\x1e/'\''o036o/g | |
| s/\x1f/'\''o037o/g | |
| s/'\''a/\\007/g | |
| s/'\''b/\\010/g | |
| s/'\''t/\\t/g | |
| s/'\''n/\\n/g | |
| s/'\''v/\\013/g | |
| s/'\''f/\\014/g | |
| s/'\''r/\\015/g | |
| s/'\''[eE]/\\033/g | |
| s/'\''\(x..\)/\\\1/g | |
| s/'\''o\([^o]*\)o/\\\1/g | |
| s/'\''\(u[^u]*\)u/\\\1/g | |
| s/'\''\(U[^U]*\)U/\\\1/g | |
| s/'\''q/'\''\\'\'''\''/g | |
| s/^\([^=]*=\)\(.*\)/\1'\''\2'\''/ | |
| s/'\'''\''$// | |
| p | |
| }' || { echo "take_snapshot: builtin 'set' failed with status $?" >&2; return 125; } | |
| test "$#" -eq 2 || { echo "take_snapshot: unexpected arg count: $# != 2" >&2; return 125; } | |
| if test "${ZSH_VERSION+y}" && (emulate sh) >/dev/null 2>&1 | |
| then emulate sh -c 'set | LANGUAGE=C LC_ALL=C sed -n "$2"' | |
| else set | LANGUAGE=C LC_ALL=C sed -n "$2" | |
| fi | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment