'POSIX shell equivalent of bash "declare -p"

I'm writing a POSIX shell function based on bash declare -p behaviour, the difference being that it targets POSIX shells.

I've set a few goals:

  1. Make a robust function
    => OK for the current code (I think).
  2. Don't define any variable (to avoid clashes)
    => OK, the current implementation doesn't define any variable
  3. As little as possible forks
    => Currently 2 forks. A single one would be great, but I'm not sure that it is possible without defining any variable.
  4. A return status of 1 when an invalid variable name is given as argument
    => That's not currently the case The current behavior is to process the good arguments and to skip the invalid ones (printing an error message and setting the return status to 1), just like declare -p does.

My current problem is in #4 but any advice about the other points is welcome ;-)

Here's the code. The first awk validates the arguments and generates a shell command for updating $@, which is then processed with eval. The second awk will then print the variables declarations with single-quote escaping:

edit: fixed the code based on @thatotherguy suggestion

#!/bin/sh

declare_p() {
    eval "$(
        awk -v funcname="declare_p" '
            BEGIN {
                output = "set --"
                for (i = 2; i < ARGC; i++) {
                    if (ARGV[i] ~ /^[[:alpha:]_][[:alnum:]_]*$/) {
                        output = output" "ARGV[i]" \042\044"ARGV[i]"\042"
                    } else {
                        print funcname": "ARGV[i]": not a valid identifier" > "/dev/stderr"
                        error = 1
                        # exit 1
                    }
                }
                print output
                exit
            }
            END { if (error) print "false" }
        ' -- "$@"
    )" # || return 1
    awk -v rc="$?" '
        BEGIN {
            for (i = 2; i < ARGC; i += 2) {
                gsub(/\047/,"\047\134\047\047",ARGV[i+1])
                print ARGV[i] "=" "\047" ARGV[i+1] "\047"
            }
            exit rc
        }
    ' -- "$@"
}
Example
#!/bin/sh

var1=" a  b "
var2="a'b"
var3="a
b"
var4="
"

declare_p var1 var2 var3 var4 var-x
echo "return code: $?"

output:

declare_p: var-x: not a valid identifier # (stderr)
var1=' a  b '
var2='a'\''b'
var3='a
b'
var4='
'
return code: 1


Solution 1:[1]

Just:

declare_p() {
    while [ "$#" -ne 0 ]; do
       if ! printf %s "$1" | grep -qx '[a-zA-Z_][a-zA-Z_0-9]*'; then
             echo "declare_p: Invalid variable name: $1" >&2
             return 1
       fi
       printf "%s='%s'\n" "$1" "$(
            eval "printf %s \"\$$1\"" |
            sed "s/'/'\\\\''/"
       )"
       shift
    done
}

# unit test
var=$(seq 255 | xargs printf %02x\\n | xxd -r -p)
eval "$(var2="$var" ; declare_p var2)"
if [ "$var" = "$var2" ]; then echo FINE; fi

Sure, you could optimize sed and grep to some magic awk - I do not think it's worth the time, if you want performance use a different programming language.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 KamilCuk