Bash - how to avoid command "eval set --" evaluating variables
As you note, eval
is evil -- and there's no need to use it here.
#!/bin/bash
# make args an array, not a string
args=( )
# replace long arguments
for arg; do
case "$arg" in
--help) args+=( -h ) ;;
--host|-hS) args+=( -s ) ;;
--cmd) args+=( -c ) ;;
*) args+=( "$arg" ) ;;
esac
done
printf 'args before update : '; printf '%q ' "$@"; echo
set -- "${args[@]}"
printf 'args after update : '; printf '%q ' "$@"; echo
while getopts "hs:c:" OPTION; do
: "$OPTION" "$OPTARG"
echo "optarg : $OPTARG"
case $OPTION in
h) usage; exit 0;;
s) servers_array+=("$OPTARG");;
c) cmd="$OPTARG";;
esac
done
That is to say: When building up a command line, append individual items to an array; you can then expand that array, quoted, without risking either evaluation or undesired behavior via effects of string-splitting, glob expansion, etc.
BDR
Updated on June 07, 2022Comments
-
BDR almost 2 years
I just write a little bash script for managing multiple parallels ssh commands. In order to parse arguments I use this piece of code :
#!/bin/bash # replace long arguments for arg in "$@"; do case "$arg" in --help) args="${args}-h ";; --host|-hS) args="${args}-s ";; --cmd) args="${args}-c ";; *) [[ "${arg:0:1}" == "-" ]] && delim='' || delim="\"" args="${args}${delim}${arg}${delim} ";; esac done echo "args before eval : $args" eval set -- $args echo "args after eval : $args" while getopts "hs:c:" OPTION; do echo "optarg : $OPTARG" case $OPTION in h) usage; exit 0;; s) servers_array+=("$OPTARG");; c) cmd="$OPTARG";; esac done
So I can use for instance -s, --host or -hS for the same result. Everything works fine except one thing.
If I put a variable in argument it will be evaluated.
Explanations
./test.sh -s SERVER -c 'echo $HOSTNAME'
cmd
should be assigned toecho $HOSTNAME
but because of theeval set
cmd is in fact assigned toecho server1
(the value of the variable)If I comment the line
eval set -- $args
I cannot use long options (--cmd) butcmd
is assigned toecho $HOSTNAME
as expected
Is there any solution to avoid eval set / getopts to evaluate variables ? So to to have the same behavior as 2. but with long options available.
Examples
with eval set
./test.sh -s SERVER -c 'echo $HOSTNAME' args before eval : -s "SERVER" -c "echo $HOSTNAME" args after eval : -s "SERVER" -c "echo $HOSTNAME" optarg : SERVER optarg : echo server1
without eval set (line
eval set -- $args
commented)./test.sh -s SERVER -c 'echo $HOSTNAME' args before eval : -s "SERVER" -c "echo $HOSTNAME" args after eval : -s "SERVER" -c "echo $HOSTNAME" optarg : SERVER optarg : echo $HOSTNAME