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:
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
}
' -- "$@"
}
#!/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
Here's a refactored version of the code, that includes @thatotherguy pointers:
#!/bin/sh
declare_p() {
eval "$(awk '
BEGIN {
printf "set --"
for (i = 1; i < ARGC; i++) {
if (ARGV[i] ~ /^[[:alpha:]_][[:alnum:]_]*$/) {
printf(" %s \"$%s\"", ARGV[i], ARGV[i])
} else {
print("declare_p: "ARGV[i]": not a valid identifier") | "cat 1>&2"
status = 1
}
}
print ""
exit status
}
' "$@" || echo false)"
awk -v status="$?" '
BEGIN {
for (i = 1; i < ARGC; i += 2) {
gsub(/\047/, "&\\\\&&", ARGV[i+1])
print ARGV[i] "=" "\047" ARGV[i+1] "\047"
}
exit status
}
' "$@"
}
The first awk
builds a command string to redefine the arguments passed to the function; for eg., given the arguments varname1
, varname2
, etc..., the outputted string will be
set -- varname1 "$varname1" varname2 "$varname2" ...
When one of the arguments isn't a valid varname (for eg. some%^@junk
), the exit status of the first awk
command is set to 1
. That will trigger the outputting of an additional false
line that sets the exit status of the eval
command to "erroneous".
Then the second awk
builds the shell commands needed for defining the variables, something like:
varname1='some value'
varname2='some other value'
...
It also "forwards" the exit status of the previous eval
command, so that you can test declare_p
with a conditional.
var1="123 456 789"
var2=" abc "
var3="line
feed"
{
declare_p var1 var2 var3 || {
echo 'oops. there was an error' 1>&2
exit 1
}
cat <<-'EOF' # a quoted heredoc doesn't expand anything
printf '<<%s>>\n' "$var1" "$var2" "$var3"
EOF
} |
ssh user@host sh
<<123 456 789>>
<< abc >>
<<line
feed>>