I wrote a similar POSIX function, but this doesn't risk arbitrary code execution:
unexport()
while case ${1##[0-9]*} in ### rule out leading numerics
(*[!_[:alnum:]]*|"") ### filter out bad|empty names
set "" ${1+"bad name: '$1'"} ### prep bad name error
return ${2+${1:?"$2"}} ### fail w/ above err or return
esac
do eval set '"$'"{$1+$1}"'" "$'"$1"'" "$'@\" ### $1 = ( $1+ ? $1 : "" )
eval "${1:+unset $1;$1=\$2;} shift 3" ### $$1 = ( $1:+ ? $2 : -- )
done
It will also handle as many arguments as you care to provide it. If an argument is a valid name that is not otherwise already set it is silently ignored. If an argument is a bad name it writes to stderr and halts as appropriate, though any valid name preceding an invalid on its command-line will still be processed.
I thought of another way. I like it a lot better.
unexport()
while unset OPTARG; OPTIND=1 ### always work w/ $1
case ${1##[0-9]*} in ### same old same old
(*[!_[:alnum:]]*|"") ### goodname && $# > 0 || break
${1+"getopts"} : "$1" ### $# ? getopts : ":"
return ### getopts errored or ":" didnt
esac
do eval getopts :s: '"$1" -"${'"$1+s}-\$$1\""
eval unset "$1; ${OPTARG+$1=\${OPTARG}#-}"
shift
done
Well, both of these use a lot of the same techniques. Basically if a shell var is unset a reference to it will not expand with a +
parameter expansion. But if it is set - regardless of its value - a parameter expansion like: ${parameter+word}
will expand to word
- and not to the variable's value. And so shell variables self-test and self-substitute on success.
They can also self-fail. In the top function if a bad name is found I move $1
into $2
and leave $1
null because the next thing I do is either return
success if all args have been processed and the loop is at an end, or, if the arg was invalid, the shell will expand the $2
into $1:?
which will kill a scripted shell and return an interrupt to an interactive one while writing word
to stderr.
In the second one getopts
does the assignments. And it won't assign a bad name - rather write it will write out a standard error message to stderr. What's more it saves the arg's value in $OPTARG
if the argument was the name of a set variable in the first place. So after doing getopts
all that is needed is to eval
a set OPTARG
's expansion into the appropriate assignment.
mktemp
if that is portable enough, and unset the value, and source the temporary file to assign the variable. At least a temporary file can be created with a more or less arbitrary name in contrast to a shell variable.sh -c
command is merely an example. Take any command within which you cannot unset a variable in its stead, if you will.