eval command in Bash and its typical uses

2019-01-03 03:36发布

After reading the bash man pages and with respect to this post.

I am still having trouble understanding what exactly the eval command does and which would be its typical uses. For example if we do:

bash$ set -- one two three  # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n}       ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n)       ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one

What exactly is happening here and how do the dollar sign and the backslash tie into the problem?

10条回答
贪生不怕死
2楼-- · 2019-01-03 04:14

I like the "evaluating your expression one additional time before execution" answer, and would like to clarify with another example.

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

The Curious results in Option 2 are that we would have passed 2 parameters as follows:

  • First Parameter: "value
  • Second Parameter: content"

How is that for counter intuitive? The additional eval will fix that.

Adapted from https://stackoverflow.com/a/40646371/744133

查看更多
老娘就宠你
3楼-- · 2019-01-03 04:15

Update: Some people say one should -never- use eval. I disagree. I think the risk arises when corrupt input can be passed to eval. However there are many common situations where that is not a risk, and therefore it is worth knowing how to use eval in any case. This stackoverflow answer explains the risks of eval and alternatives to eval. Ultimately it is up to the user to determine if/when eval is safe and efficient to use.


The bash eval statement allows you to execute lines of code calculated or acquired, by your bash script.

Perhaps the most straightforward example would be a bash program that opens another bash script as a text file, reads each line of text, and uses eval to execute them in order. That's essentially the same behavior as the bash source statement, which is what one would use, unless it was necessary to perform some kind of transformation (e.g. filtering or substitution) on the content of the imported script.

I rarely have needed eval, but I have found it useful to read or write variables whose names were contained in strings assigned to other variables. For example, to perform actions on sets of variables, while keeping the code footprint small and avoiding redundancy.

eval is conceptually simple. However, the strict syntax of the bash language, and the bash interpreter's parsing order can be nuanced and make eval appear cryptic and difficult to use or understand. Here are the essentials:

  1. The argument passed to eval is a string expression that is calculated at runtime. eval will execute the final parsed result of its argument as an actual line of code in your script.

  2. Syntax and parsing order are stringent. If the result isn't an executable line of bash code, in scope of your script, the program will crash on the eval statement as it tries to execute garbage.

  3. When testing you can replace the eval statement with echo and look at what is displayed. If it is legitimate code in the current context, running it through eval will work.


The following examples may help clarify how eval works...

Example 1:

eval statement in front of 'normal' code is a NOP

$ eval a=b
$ eval echo $a
b

In the above example, the first eval statements has no purpose and can be eliminated. eval is pointless in the first line because there is no dynamic aspect to the code, i.e. it already parsed into the final lines of bash code, thus it would be identical as a normal statement of code in the bash script. The 2nd eval is pointless too, because, although there is a parsing step converting $a to its literal string equivalent, there is no indirection (e.g. no referencing via string value of an actual bash noun or bash-held script variable), so it would behave identically as a line of code without the eval prefix.



Example 2:

Perform var assignment using var names passed as string values.

$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval

If you were to echo $key=$val, the output would be:

mykey=myval

That, being the final result of string parsing, is what will be executed by eval, hence the result of the echo statement at the end...



Example 3:

Adding more indirection to Example 2

$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing

The above is a bit more complicated than the previous example, relying more heavily on the parsing-order and peculiarities of bash. The eval line would roughly get parsed internally in the following order (note the following statements are pseudocode, not real code, just to attempt to show how the statement would get broken down into steps internally to arrive at the final result).

 eval eval \$$keyA=\$$valA  # substitution of $keyA and $valA by interpreter
 eval eval \$keyB=\$valB    # convert '$' + name-strings to real vars by eval
 eval $keyB=$valB           # substitution of $keyB and $valB by interpreter
 eval that=amazing          # execute string literal 'that=amazing' by eval

If the assumed parsing order doesn't explain what eval is doing enough, the third example may describe the parsing in more detail to help clarify what is going on.



Example 4:

Discover whether vars, whose names are contained in strings, themselves contain string values.

a="User-provided"
b="Another user-provided optional value"
c=""

myvarname_a="a"
myvarname_b="b"
myvarname_c="c"

for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
    eval varval=\$$varname
    if [ -z "$varval" ]; then
        read -p "$varname? " $varname
    fi
done

In the first iteration:

varname="myvarname_a"

Bash parses the argument to eval, and eval sees literally this at runtime:

eval varval=\$$myvarname_a

The following pseudocode attempts to illustrate how bash interprets the above line of real code, to arrive at the final value executed by eval. (the following lines descriptive, not exact bash code):

1. eval varval="\$" + "$varname"      # This substitution resolved in eval statement
2. .................. "$myvarname_a"  # $myvarname_a previously resolved by for-loop
3. .................. "a"             # ... to this value
4. eval "varval=$a"                   # This requires one more parsing step
5. eval varval="User-provided"        # Final result of parsing (eval executes this)

Once all the parsing is done, the result is what is executed, and its effect is obvious, demonstrating there is nothing particularly mysterious about eval itself, and the complexity is in the parsing of its argument.

varval="User-provided"

The remaining code in the example above simply tests to see if the value assigned to $varval is null, and, if so, prompts the user to provide a value.

查看更多
不美不萌又怎样
4楼-- · 2019-01-03 04:19

Simply think of eval as "evaluating your expression one additional time before execution"

eval echo \${$n} becomes echo $1 after the first round of evaluation. Three changes to notice:

  • The \$ became $ (The backslash is needed, otherwise it tries to evaluate ${$n}, which means a variable named {$n}, which is not allowed)
  • $n was evaluated to 1
  • The eval disappeared

In the second round, it is basically echo $1 which can be directly executed.

So eval <some command> will first evaluate <some command> (by evaluate here I mean substitute variables, replace escaped characters with the correct ones etc.), and then run the resultant expression once again.

eval is used when you want to dynamically create variables, or to read outputs from programs specifically designed to be read like this. See http://mywiki.wooledge.org/BashFAQ/048 for examples. The link also contains some typical ways in which eval is used, and the risks associated with it.

查看更多
小情绪 Triste *
5楼-- · 2019-01-03 04:22

The eval statement tells the shell to take eval’s arguments as command and run them through the command-line. It is useful in a situation like below:

In your script if you are defining a command into a variable and later on you want to use that command then you should use eval:

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >
查看更多
对你真心纯属浪费
6楼-- · 2019-01-03 04:22

I originally intentionally never learned how to use eval, because most people will recommend to stay away from it like the plague. However I recently discovered a use case that made me facepalm for not recognizing it sooner.

If you have cron jobs that you want to run interactively to test, you might view the contents of the file with cat, and copy and paste the cron job to run it. Unfortunately, this involves touching the mouse, which is a sin in my book.

Lets say you have a cron job at /etc/cron.d/repeatme with the contents:

*/10 * * * * root program arg1 arg2

You cant execute this as a script with all the junk in front of it, but we can use cut to get rid of all the junk, wrap it in a subshell, and execute the string with eval

eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)

The cut command only prints out the 6th field of the file, delimited by spaces. Eval then executes that command.

I used a cron job here as an example, but the concept is to format text from stdout, and then evaluate that text.

The use of eval in this case is not insecure, because we know exactly what we will be evaluating before hand.

查看更多
在下西门庆
7楼-- · 2019-01-03 04:22

You asked about typical uses.

One common complaint about shell scripting is that you (allegedly) can't pass by reference to get values back out of functions.

But actually, via "eval", you can pass by reference. The callee can pass back a list of variable assignments to be evaluated by the caller. It is pass by reference because the caller can allowed to specify the name(s) of the result variable(s) - see example below. Error results can be passed back standard names like errno and errstr.

Here is an example of passing by reference in bash:

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

The output looks like this:

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

There is almost unlimited band width in that text output! And there are more possibilities if the multiple output lines are used: e.g., the first line could be used for variable assignments, the second for continuous 'stream of thought', but that's beyond the scope of this post.

查看更多
登录 后发表回答