BashWhen to use eval

Introduction

First and foremost: know what you're doing! Secondly, while you should avoid using eval, if its use makes for cleaner code, go ahead.

Using Eval

For example, consider the following that sets the contents of [email protected] to the contents of a given variable:

a=(1 2 3)
eval set -- "${a[@]}"

This code is often accompanied by getopt or getopts to set [email protected] to the output of the aforementioned option parsers, however, you can also use it to create a simple pop function that can operate on variables silently and directly without having to store the result to the original variable:

isnum()
{
    # is argument an integer?
    local re='^[0-9]+$'
    if [[ -n $1 ]]; then
        [[ $1 =~ $re ]] && return 0
        return 1
    else
        return 2
    fi
}

isvar()
{
    if isnum "$1"; then
        return 1
    fi
    local arr="$(eval eval -- echo -n "\$$1")"
    if [[ -n ${arr[@]} ]]; then
        return 0
    fi
    return 1
}

pop()
{
    if [[ -z [email protected] ]]; then
        return 1
    fi

    local var=
    local isvar=0
    local arr=()
    
    if isvar "$1"; then # let's check to see if this is a variable or just a bare array
        var="$1"
        isvar=1
        arr=($(eval eval -- echo -n "\${$1[@]}")) # if it is a var, get its contents
    else
        arr=([email protected])
    fi
    
    # we need to reverse the contents of [email protected] so that we can shift
    # the last element into nothingness
    arr=($(awk <<<"${arr[@]}" '{ for (i=NF; i>1; --i) printf("%s ",$i); print $1; }'

    # set [email protected] to ${arr[@]} so that we can run shift against it.
    eval set -- "${arr[@]}"
    
    shift # remove the last element
    
    # put the array back to its original order
    arr=($(awk <<<"[email protected]" '{ for (i=NF; i>1; --i) printf("%s ",$i); print $1; }'
    
    # echo the contents for the benefit of users and for bare arrays
    echo "${arr[@]}"

    if ((isvar)); then
        # set the contents of the original var to the new modified array
        eval -- "$var=(${arr[@]})" 
    fi
}

Using Eval with Getopt

While eval may not be needed for a pop like function, it is however required whenever you use getopt:

Consider the following function that accepts -h as an option:

f()
{
    local __me__="${FUNCNAME[0]}"
    local argv="$(getopt -o 'h' -n $__me__ -- "[email protected]")"

    eval set -- "$argv"
    
    while :; do
        case "$1" in
            -h)
              echo "LOLOLOLOL"
              return 0
              ;;
            --)
              shift
              break
              ;;
    done
    
    echo "[email protected]"
}

Without eval set -- "$argv" generates -h -- instead of the desired (-h --) and subsequently enters an infinite loop because -h -- doesn't match -- or -h.