How to Use eval in Linux Bash Scripts
Of all the bash commands, poor old man eval
probably has the worst reputation. Valid or just bad press? We discuss the uses and dangers of this least-loved Linux command.
We need to talk about eval
used carelessly, eval
can lead to unpredictable behavior and even system insecurity. From the sound of it, we probably shouldn’t be using it, right? Not quite.
The same could be said about cars. In the wrong hands, they are a deadly weapon. People use them in ramming attacks and as getaway vehicles. Should we all give up the car? No of course not. But they have to be used properly and by people who know how to drive them.
The usual adjective applies to eval
is “evil”. But it all depends on how it is used. That eval
command sorts the Values from one or more variables. It creates a command string. It then executes this command. This makes it useful when you need to deal with situations where the content of a command is dynamically inferred during the execution of your script.
Problems arise when writing a script to use it eval
on a string received from somewhere Outside the script. It can be entered by a user, sent via an API, tagged to an HTTPS request, or used elsewhere outside of the script.
If the string that eval
will work, has not been derived locally and programmatically, there is a risk that the string contains embedded malicious instructions or other malformed input. Obviously you don’t want to eval
execute malicious commands. So don’t use it to be on the safe side eval
with externally generated strings or user input.
Getting started with eval
That eval
The command is a built-in bash shell command. If bash is present, eval
will be there.
eval
concatenates its parameters into a single string. A single space is used to separate concatenated elements. It evaluates the arguments and then passes the entire string to the shell for execution.
Let’s create a variable called wordcount
.
wordcount="wc -w raw-notes.md"
The String variable contains a word count command in a file called raw-notes.md.
we can use eval
to run this command by passing it the value of the variables.
The command runs in the current shell, not in a subshell. We can easily show that. We have a short text file called “variables.txt”. It contains these two lines.
first=How-To second=Geek
We will use cat
to send those lines to the terminal window. Then we will use eval
evaluate a cat
Command to run the instructions in the text file. This sets the variables for us.
cat variables.txt eval "$(cat variables.txt)" echo $first $second
Through use echo
To print the values of the variables, we can see that eval
The command runs in the current shell, not in a subshell.
A process in a subshell cannot change the shell environment of the parent process. Since eval runs in the current shell, the variables are set by eval
can be used by the shell that started the eval
Command.
Note that when you use eval
in a script, the shell that would be changed eval
is the subshell the script is running in, not the shell that started it.
TIED TOGETHER: How to use Linux cat and tac commands
Using variables in the command string
We can include other variables in the command strings. We will set two variables to contain integers.
num1=10 num2=7
We create a variable that contains a expr
Command that returns the sum of two numbers. This means that we need to access the values of the two integer variables in the command. Notice the backticks around the expr
Expression.
add="`expr $num1 + $num2`"
Let’s create another command to show us the result of expr
Expression.
show="echo"
Note that we don’t need to add a space at the end of echo
character string, still at the beginning of the expr
Line. eval
takes care of.
And to run the whole command we use:
eval $show $add
The variable values within the expr
string are replaced by eval
before passing it to the shell for execution.
TIED TOGETHER: How to work with variables in Bash
Access to variables within variables
You can assign a value to a variable and then assign the Surname this variable to another variable. Use eval
you can access it value held in the first variable, by their name, which is the value stored in the second variable. An example will help you unravel this.
Copy this script into an editor and save it as a file named assign.sh.
#!/bin/bash title="How-To Geek" webpage=title command="echo" eval $command \${$webpage}
We need to make it executable with the chmod
Command.
chmod +x assign.sh
You must do this for all scripts that you copy from this article. Just use the appropriate script name in each case.
When we run our script, we see the text from the variable title
although the eval
command uses the variable webpage
.
./assign.sh
The Escaped Dollar Sign”$
” and the brackets “{}
” causes eval to look at the value in the variable whose name is stored in webpage
Variable.
Use dynamically created variables
we can use eval
Create variables dynamically. This script is called “loop.sh”.
#!/bin/bash total=0 label="Looping complete. Total:" for n in {1..10} do eval x$n=$n echo "Loop" $x$n ((total+=$x$n)) done echo $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10 echo $label $total
It creates a variable called total
which contains the sum of the values of the variables we created. It then creates a string variable named label
. This is a simple text string.
We will iterate through 10 loops and create 10 variables named x1
up to x10
. That eval
statement in the body of the loop returns the “x” and takes the value of the loop counter $n
to create the variable name. At the same time, it sets the new variable to the value of the loop counter $n
.
It prints the new variable in the terminal window and then increments the total
Variable with the value of the new variable.
Outside the loop, the 10 new variables are printed again, all on one line. Note that we can also refer to the variables by their real names without using a computed or derived version of their names.
Finally, we print out the value of total
Variable.
./loop.sh
TIED TOGETHER: Primer: Bash loops: for, while and until
Using eval with arrays
Consider a scenario where you have a script that runs for a long time and does some processing for you. It writes to a log file with a name constructed from a timestamp. Occasionally a new log file is created. When the script finishes and there are no errors, it deletes the log files it created.
They don’t want it easy rm *.log
, you just want it to delete the log files it creates. This script simulates this functionality. This is clear-logs.sh.
#!/bin/bash declare -a logfiles filecount=0 rm_string="echo" function create_logfile() { ((++filecount)) filename=$(date +"%Y-%m-%d_%H-%M-%S").log logfiles[$filecount]=$filename echo $filecount "Created" ${logfiles[$filecount]} } # body of the script. Some processing is done here that # periodically generates a log file. We'll simulate that create_logfile sleep 3 create_logfile sleep 3 create_logfile sleep 3 create_logfile # are there any files to remove? for ((file=1; file<=$filecount; file++)) do # remove the logfile eval $rm_string ${logfiles[$file]} "deleted..." logfiles[$file]="" done
The script declares an array named logfiles
. This contains the names of the log files created by the script. It declares a variable called filecount
. This contains the number of log files created.
It also declares a string called rm_string
. In a real script, this would include the rm
command but we use echo
This allows us to demonstrate the principle non-destructively.
The function create_logfile()
This is where each log file is named and where it will be opened. We only create those filenameand pretend it was created in the file system.
The function increases the filecount
Variable. Its initial value is zero, so the first filename we create will be stored at position one in the array. This is done on purpose, see later.
The file name is created with the date
command and the .log extension. The name is stored in the array at the position specified by filecount
. The name is displayed in the terminal window. In a real script, you would also create the actual file.
The main part of the script is simulated with sleep
Command. It creates the first log file, waits three seconds, and then creates another. It creates four log files distributed in such a way that the timestamps in their filenames are different.
Finally there is a loop that deletes the log files. The loop counter file is set to one. It counts up to and including the value of filecount
which contains the number of files created.
if filecount
is still set to zero—because no log files were created—the loop body will never execute, since one is not less than or equal to zero. Therefore the filecount
variable was set to zero at declaration and why it was incremented before The first file has been created.
Inside the loop we use eval
with our non-destructive rm_string
and the name of the file retrieved from the array. Then we set the array element to an empty string.
This is what we see when we run the script.
./clear-logs.sh
It’s not all bad
Much slandered eval
definitely has its uses. Like most tools, it is dangerous to use recklessly, and in more ways than one.
If you make sure the strings it’s working on are created internally and aren’t captured by humans, APIs, or things like HTTPS requests, you’ll avoid the biggest pitfalls.
TIED TOGETHER: How to show date and time in Linux terminal (and use it in bash scripts)