The 'eval' command in Bash and its typical uses
Asked Answered
J

11

200

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:

$ set -- one two three  # Sets $1 $2 $3
$ echo $1
one

$ n=1
$ echo ${$n}       ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution

$ echo $($n)       ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found

$ 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?

Juvenile answered 16/6, 2012 at 16:13 Comment(4)
For the record, the second attempt works. $($n) runs $n in a subshell. It tries to run the command 1 which does not exist.Deserve
@MartinWickman But the requirement is to run echo $1 eventually, not 1. I don't think it can be done using subshells.Morelli
You should be aware of the security implications of using eval.Dipody
@Raze2dust: I don't believe he was suggesting it could be run with subshells, but rather explaining why the 5th command the OP listed didn't work.Racquelracquet
N
232

eval takes a string as its argument, and evaluates it as if you'd typed that string on a command line. (If you pass several arguments, they are first joined with spaces between them.)

${$n} is a syntax error in bash. Inside the braces, you can only have a variable name, with some possible prefix and suffixes, but you can't have arbitrary bash syntax and in particular you can't use variable expansion. There is a way of saying “the value of the variable whose name is in this variable”, though:

echo ${!n}
one

$(…) runs the command specified inside the parentheses in a subshell (i.e. in a separate process that inherits all settings such as variable values from the current shell), and gathers its output. So echo $($n) runs $n as a shell command, and displays its output. Since $n evaluates to 1, $($n) attempts to run the command 1, which does not exist.

eval echo \${$n} runs the parameters passed to eval. After expansion, the parameters are echo and ${1}. So eval echo \${$n} runs the command echo ${1}.

Note that most of the time, you must use double quotes around variable substitutions and command substitutions (i.e. anytime there's a $): "$foo", "$(foo)". Always put double quotes around variable and command substitutions, unless you know you need to leave them off. Without the double quotes, the shell performs field splitting (i.e. it splits value of the variable or the output from the command into separate words) and then treats each word as a wildcard pattern. For example:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *

eval is not used very often. In some shells, the most common use is to obtain the value of a variable whose name is not known until runtime. In bash, this is not necessary thanks to the ${!VAR} syntax. eval is still useful when you need to construct a longer command containing operators, reserved words, etc.

Negligent answered 16/6, 2012 at 16:32 Comment(14)
With regard to my comment above, how many "passes" does the eval do?Juvenile
@Konos5 What comment? eval receives a string (which may itself be the result of parsing and evaluation), and interprets it as a code snippet.Karwan
Under Raze2dust's answer I've left a comment. I now tend to believe that eval is mostly used for dereferencing purposes. If I type eval echo \${$n} I get one. However if I type echo \${$n} I get \${1}. I believe this is happening due to eval's "two-pass" parsing. I am now wondering what would happen if I need to triple dereference using an extra i=n declaration. In this case according to Raze2dust I just need to put an extra eval. I believe however that there should be a better way... (it can get easily cluttered)Juvenile
@Konos5 I wouldn't use eval eval. I can't remember ever feeling the need. If you really need two eval passes, use a temporary variable, it'll be easier to debug: eval tmp="\${$i}"; eval x="\${$tmp}".Karwan
Raze2dust gave this link mywiki.wooledge.org/BashFAQ/048. In the very first paragraph it states that eval parses the code twice. This makes perfect sense because in your effort to simplify debugging you now introduced the extra variable tmp. Thus now we had to do 4 "dereferences" in total which where no problem for the 2 evals (even in separate commands) which parse twice. The only thing still troubling me is the syntax Raze2dust used for the triple dereferencing...Juvenile
@Konos5 "Parsed twice" is slightly misleading. Some people might be led to believe this due to the difficulty of specifying a literal string argument in Bash that's protected from various expansions. eval just takes code in a string and evaluates it according to the usual rules. Technically, it isn't even correct, because there are a few corner cases in which Bash modifies parsing to not even perform expansions to the arguments of eval - but that's a very obscure tidbit that I doubt anybody knows about.Posterity
@Gilles: I dispute your boldfaced assertion "Always put double quotes around variable and command substitutions" for the case of command substitution, this line should prove quotes are not needed? Notice the value of v: v=$(echo 'a<space><space><space>b'); echo "$v"Sensuality
@Sensuality Always is a simple, easy-to-remember rule. There are a few situations where the quotes are optional, and assignments are indeed one of them. You can rembember the detailed rules, or you can just remember to use double quotes all the time and not get in trouble.Karwan
@Gilles: so you meant "it's easier to get into the habit of doing this, it does no harm yet it prevents damage in corner cases". Then, I could not agree more with you. You just used a categorical tone which is exaggerated and out of place. Apart from this, you know I admire you for the generosity with which you share your knowledge on this forum and keep teaching us stuff!Sensuality
@Giles ${!VAR} syntax great tip. I've been struggling with substitutions in my scripts and this makes things much easier. I just made a select statement to ssh into different machines and was having trouble getting the $REPLY to pick the server out of an array of servers. ssh ${!servers[$REPLY-1]} did the trick.Concavity
I studied your answer but I have some questions: unix.stackexchange.com/questions/383862/…Schlemiel
How about with respect to sudo as at How to get home dir of the current user in shell script file?Pilau
I am facing difficulty in creating dynamic indexes in mongo db, i am doing this through bash script: mongo --eval 'db.getCollection(${collectionname}).createIndex({"Company": 1})'. Here ${collectionname} should be replaced with the acutal value residing in this variable. So this expression should evaluate to: db.getCollection("business").createIndex({"Company": 1})' Please help!Newfoundland
Asked: #61972749Newfoundland
M
49

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 Eval command and security issues for examples. The link also contains some typical ways in which eval is used, and the risks associated with it.

Morelli answered 16/6, 2012 at 16:19 Comment(10)
As a note for the first bullet, the ${VAR} syntax is allowed, and preferred when there is any ambiguity (does $VAR == $V, followed by AR or $VAR == $VA followed by R). ${VAR} is equivalent to $VAR. In fact, its the variable name of $n that is not allowed.Racquelracquet
-jedwards- Well observed point! @Raze2dust & Gilles: I read both the answers and from what I understand the echo command along with the '$' symbol serve as some sort of double dereferencing. However if I just type echo \${$n} I don't get the same result as with eval. Since eval "evaluates it as if you'd typed that string on a command line" what was changed this time?Juvenile
Could you also suggest what would happen if I needed to do a triple dereference? i.e. set -- one two three && n=1 && i=nJuvenile
eval eval echo \\\${\${$i}} will do a triple dereference. I am not sure if there's a simpler way to do that. Also, \${$n} works fine (prints one) on my machine..Morelli
echo \${$n} prints ${1} on mine. I believe this is the correct behaviour. Could elaborate on eval eval echo \\\${\${$i}}?Juvenile
@Konos5 echo \\\${\${$i}} prints \${${n}}. eval echo \\\${\${$i}} is equivalent to echo \${${n}}`` and prints ${1}. eval eval echo \\\${\${$i}}` is equivalent to eval echo ${1} and prints one.Karwan
@Gilles One more thing.. Could you explain how the first 3 backslashes and the dollar sign work in more detail..? I am really struggling to get my head around them..! You said above that echo \\\${\${$i}} prints \${${n}}. I however believe that the first \ escapes the second \ and therefore should be \\${${n}} instead. Could you please be more specific on that?Juvenile
@Konos5 Think along the same lines - The first ` escapes the second one, and the third ` escapes the $ after that. So it becomes \${${n}} after one round of evaluationMorelli
@Raze2dust Are you saying that the -first- backslash after the first round of evaluation here: \${${n}} is essentially the -second- one from the original sequence here: \\\${\${$i}}? If that's the case then that would imply left-to-right evaluation and only for the first character on the right...Juvenile
@Konos5 Left-to-right is the right way to think of for quote and backslash parsing. First \\ yielding one backslash. Then \$ yielding one dollar. And so on.Karwan
N
41

In my experience, a "typical" use of eval is for running commands that generate shell commands to set environment variables.

Perhaps you have a system that uses a collection of environment variables, and you have a script or program that determines which ones should be set and their values. Whenever you run a script or program, it runs in a forked process, so anything it does directly to environment variables is lost when it exits. But that script or program can send the export commands to standard output.

Without eval, you would need to redirect standard output to a temporary file, source the temporary file, and then delete it. With eval, you can just:

eval "$(script-or-program)"

Note the quotes are important. Take this (contrived) example:

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier

$ eval "$(python test.py)"
I got activated!
Nitrochloroform answered 22/10, 2014 at 17:29 Comment(6)
have any examples of common tools that do this? The tool itself has a means to produce a set of shell commands that can be passed to eval?Vitelline
@Joakim I don't know of any opensource tools that do it, but it was used in some private scripts at companies where I've worked. I just started using this technique again myself with xampp. Apache .conf files expand environment variables written ${varname}. I find it convenient to use identical .conf files on several different servers with just a few things parameterized by environment variables. I edited /opt/lampp/xampp (which starts apache) to do this kind of eval with a script that pokes around the system and outputs bash export statements to define variables for the .conf files.Nitrochloroform
@Joakim The alternative would be to have a script to generate each of the affected .conf files from a template, based on the same poking around. One thing I like better about my way is that starting apache without going through /opt/lampp/xampp does not use stale output scripts, but rather it fails to start because the environment variables expand to nothing and create invalid directives.Nitrochloroform
@Anthony Sottile I see you edited the answer to add quotes around $(script-or-program), saying that they were important when running multiple commands. Can you please provide an example - the following works fine with semi-colon separated commands in the stdout of foo.sh: echo '#!/bin/bash' > foo.sh; echo 'echo "echo -n a; echo -n b; echo -n c"' >>foo.sh; chmod 755 foo.sh; eval $(./foo.sh). This produces abc on stdout. Running ./foo.sh produces: echo -n a; echo -n b; echo -n cNitrochloroform
This is great answer! I found it when searching for bash eval "$ in google because I've been finding this syntax in many online tutorials for different things. Example of typical use is eval "$(ssh-agent -s)" in github tutorialAllergist
For an example of a common tool that uses eval, see pyenv. pyenv lets you easily switch between multiple versions of Python. You put eval "$(pyenv init -)" into your .bash_profile (or similar) shell config file. That constructs a little shell script then evaluates it in the current shell.Deutzia
M
14

The eval statement tells the shell to take eval’s arguments as commands 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:

a="ls | more"
$a

Output:

bash: command not found: ls | more

The above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there:

eval $a

Output:

file.txt
mailids
remote_cmd.sh
sample.txt
tmp

Marentic answered 23/9, 2014 at 9:12 Comment(0)
R
7

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.

Repast answered 26/10, 2017 at 0:28 Comment(0)
S
3

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.

Saiff answered 5/1, 2018 at 19:12 Comment(0)
P
2

I've recently had to use eval to force multiple brace expansions to be evaluated in the order I needed. Bash does multiple brace expansions from left to right, so

xargs -I_ cat _/{11..15}/{8..5}.jpg

expands to

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

but I needed the second brace expansion done first, yielding

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

The best I could come up with to do that was

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

This works because the single quotes protect the first set of braces from expansion during the parsing of the eval command line, leaving them to be expanded by the subshell invoked by eval.

There may be some cunning scheme involving nested brace expansions that allows this to happen in one step, but if there is I'm too old and stupid to see it.

Proportional answered 5/1, 2018 at 9:16 Comment(0)
D
1

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 two parameters as follows:

  • First parameter: "par1
  • Second parameter: par2"

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

It was adapted from another answer on How can I reference a file for variables using Bash?

Duclos answered 17/11, 2016 at 4:38 Comment(1)
"par" is a little bit too short. Perhaps "para" or "param"?Wixted
S
1

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.

Sissel answered 18/3, 2018 at 10:34 Comment(1)
saying that there are "more possibilities" is trifle, trivial and redundant to say the least.Bedrabble
R
0

In the question:

who | grep $(tty | sed s:/dev/::)

outputs errors claiming that files a and tty do not exist. I understood this to mean that tty is not being interpreted before execution of grep, but instead that bash passed tty as a parameter to grep, which interpreted it as a file name.

There is also a situation of nested redirection, which should be handled by matched parentheses which should specify a child process, but bash is primitively a word separator, creating parameters to be sent to a program, therefore parentheses are not matched first, but interpreted as seen.

I got specific with grep, and specified the file as a parameter instead of using a pipe. I also simplified the base command, passing output from a command as a file, so that i/o piping would not be nested:

grep $(tty | sed s:/dev/::) <(who)

works well.

who | grep $(echo pts/3)

is not really desired, but eliminates the nested pipe and also works well.

In conclusion, bash does not seem to like nested pipping. It is important to understand that bash is not a new-wave program written in a recursive manner. Instead, bash is an old 1,2,3 program, which has been appended with features. For purposes of assuring backward compatibility, the initial manner of interpretation has never been modified. If bash was rewritten to first match parentheses, how many bugs would be introduced into how many bash programs? Many programmers love to be cryptic.

Ress answered 28/9, 2017 at 4:46 Comment(0)
M
0

As clearlight has said, "(p)erhaps 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". I'm no expert, but the textbook I'm currently reading (Shell-Programmierung by Jürgen Wolf) points to one particular use of this that I think would be a valuable addition to the set of potential use cases collected here.

For debugging purposes, you may want to go through your script line by line (pressing Enter for each step). You could use eval to execute every line by trapping the DEBUG signal (which I think is sent after every line):

trap 'printf "$LINENO :-> " ; read line ; eval $line' DEBUG
Mcclintock answered 31/3, 2022 at 9:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.