You can do this in bash without eval (and without artificial escaping):
for arg in "$@"; do
if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; then
declare +i +a +A "$arg"
fi
done
Edit: Based on a comment by Stéphane Chazelas, I added flags to the declare to avoid having the variable assigned being already declared as an array or integer variable, which will avoid a number of cases in which declare
will evaluate the value part of the key=val
argument. (The +a
will cause an error if the variable to be set is already declared as an array variable, for example.) All of these vulnerabilities relate to using this syntax to reassign existing (array or integer) variables, which would typically be well-known shell variables.
In fact, this is just an instance of a class of injection attacks which will equally affect eval
-based solutions: it would really be much better to only allow known argument names than to blindly set whichever variable happened to be present in the command-line. (Consider what happens if the command line sets PATH
, for example. Or resets PS1
to include some evaluation which will happen at the next prompt display.)
Rather than use bash variables, I'd prefer to use an associative array of named arguments, which is both easier to set, and much safer. Alternatively, it could set actual bash variables, but only if their names are in an associative array of legitimate arguments.
As an example of the latter approach:
# Could use this array for default values, too.
declare -A options=([bs]= [if]= [of]=)
for arg in "$@"; do
# Make sure that it is an assignment.
# -v is not an option for many bash versions
if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= &&
${options[${arg%%=*}]+ok} == ok ]]; then
declare "$arg"
# or, to put it into the options array
# options[${arg%%=*}]=${arg#*=}
fi
done