Bash - how to avoid command "eval set --" evaluating variables

12,739

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.

Share:
12,739
BDR
Author by

BDR

Updated on June 07, 2022

Comments

  • BDR
    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'
    
    1. cmd should be assigned to echo $HOSTNAME but because of the eval set cmd is in fact assigned to echo server1 (the value of the variable)

    2. If I comment the line eval set -- $args I cannot use long options (--cmd) but cmd is assigned to echo $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