How to Use eval in Linux Bash Scripts

Estimated read time 9 min read

[ad_1]

Linux laptop showing a bash prompt
fatmawati achmad zaenuri/Shutterstock.com

Of all the Bash commands, poor old eval probably has the worst reputation. Justified, or just bad press? We discuss the use and dangers of this least-loved of Linux commands.

We Need To Talk About eval

Used carelessly, eval can lead to unpredictable behavior and even system insecurities. From the sounds of it, we should probably not use it, right? Well not quite.

You could say something similar about automobiles. In the wrong hands, they’re a deadly weapon. People use them in ram-raids and as get-away vehicles. Should we all stop using cars? No, of course not. But they have to be used properly, and by people who know how to drive them.

The usual adjective applied to eval is “evil.” But it all comes down to how it’s being used. The eval command collates the values from one or more variables. It creates a command string. It then executes that command. This makes it useful when you need to cope with situations where the content of a command is derived dynamically during the execution of your script.

Problems arise when a script is written to use eval on a string that has been received from somewhere outside the script. It may be typed in by a user, sent through an API, tagged onto an HTTPS request, or anywhere else external to the script.

If the string that eval is going to work on was not derived locally and programmatically, there is a risk that the string might contain embedded malicious instructions or other badly-formed input. Obviously, you don’t want eval to execute malicious commands. So to be safe, don’t use eval with externally-generated strings or user input.

First Steps With eval

The eval command is a built-in Bash shell command. If Bash is present, eval will be present.

eval concatenates its parameters into a single string. It will use a single space to separate concatenated elements. It evaluates the arguments and then passes the entire string to the shell to execute.

Let’s create a variable called wordcount.

wordcount="wc -w raw-notes.md"

The string variable contains a command to count the words in a file called “raw-notes.md.”

We can use eval to execute that command by passing it the value of the variable.

Using eval with a string variable to count the words in a file

The command is executed in the current shell, not in a subshell. We can easily show this. We’ve got a short text file called “variables.txt.” It contains these two lines.

first=How-To
second=Geek

We’ll use cat to send these lines to the terminal window. Then we’ll use eval to evaluate a cat command so that the instructions inside the text file are acted on. This will set the variables for us.

cat variables.txt
eval "$(cat variables.txt)"
echo $first $second

Accessing variables set by eval in the current shell

By using echo to print the values of the variables we can see that eval command runs in the current shell, not a subshell.

A process in a subshell cannot change the shell environment of the parent. Because eval runs in the current shell, the variables set by eval are usable from the shell that launched the eval command.

Note that if you use eval in a script, the shell that would be altered by eval is the subshell that the script is running in, not the shell that launched it.

RELATED: How to Use the Linux cat and tac Commands

Using Variables in the Command String

We can include other variables in the command strings. We’ll set two variables to hold integers.

num1=10 
num2=7

We’ll create a variable to hold an expr command that will return the sum of two numbers. This means we need to access the values of the two integer variables in the command. Note the backticks around the expr statement.

add="`expr $num1 + $num2`"

We’ll create another command to show us the result of the expr statement.

show="echo"

Note that we don’t need to include a space at the end of the echo string, nor at the start of the expr string. eval takes care of that.

And to execute the entire command we use:

eval $show $add

Using variables in the command string

The variable values inside the expr string are substituted into the string by eval , before it is passed to the shell to be executed.

RELATED: How to Work with Variables in Bash

Accessing Variables Inside Variables

You can assign a value to a variable, and then assign the name of that variable to another variable. Using eval, you can access the value held in the first variable, from its name which is the value stored in the second variable. An example will help you untangle that.

Copy this script to an editor, and save it as a file called “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

Using chmod to make a script executable

You’ll need to do this for any scripts 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 even though the eval command is using the variable webpage.

./assign.sh

Accessing the value of a variable from its name stored in another variable

The escaped dollar sign “$” and the braces “{}” cause eval to look at the value held inside the variable whose name is stored in the webpage variable.

Using Dynamically Created Variables

We can use eval to 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 holds the sum of the values of the variables we create. It then creates a string variable called label. This is a simple string of text.

We’re going to loop 10 times and create 10 variables called x1 up to x10. The eval statement in the body of the loop provides 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 to the terminal window and then increments the total variable with the value of the new variable.

Outside of the loop, the 10 new variables are printed once more, all on one line. Note that we can refer to the variables by their real names too, without using a calculated or derived version of their names.

Finally, we print the value of the total variable.

./loop.sh

Using eval to dynamically create variables

RELATED: Primer: Bash Loops: for, while, and until

Using eval With Arrays

Imagine a scenario where you have a script that is long-running and performing some processing for you. It writes to a log file with a name created from a time stamp. Occasionally, it will start a new log file. When the script has finished, if there have been no errors, it deletes the log files it has created.

You don’t want it to simply rm *.log, you only want it to delete the log files it has created. This script simulates that 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 called logfiles . This will hold the names of the log files that are created by the script. It declares a variable called filecount . This will hold the number of log files that have been created.

It also declares a string called rm_string. In a real-world script, this would contain the rm command, but we’re using echo so we can demonstrate the principle in a non-destructive fashion.

The function create_logfile() is where each log file is named, and where it would be opened. We’re only creating the filename, and pretending it has been created in the file system.

The function increments the filecount variable. Its initial value is zero, so the first filename we create is stored at position one in the array. This is done on purpose, as well see later.

The filename is created using the date command, and the “.log” extension. The name is stored in the array at the position indicated by filecount. The name is printed to the terminal window. In a real-world script, you’d also create the actual file.

The body of the script is simulated using the sleep command. It creates the first log file, waits three seconds, and then creates another. It creates four log files, spaced out so 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 holds the number of files that were created.

If filecount is still set to zero—because no log files were created—the loop body will never be executed because one is not less than or equal to zero. That’s why the filecount variable was set to zero when it was declared and why it was incremented before the first file was created.

Inside the loop, we use eval with our non-destructive rm_string and the name of the file which is retrieved from the array. We then set the array element to an empty string.

This is what we see when we run the script.

./clear-logs.sh

Deleting files whose names are stored in an array

It’s Not All Bad

Much-maligned eval definitely has its uses. Like most tools, used recklessly it is dangerous, and in more ways than one.

If you make sure the strings it works on are created internally and not captured from humans, APIs, or things like HTTPS requests, you’ll avoid the major pitfalls.

RELATED: How to Display the Date and Time in the Linux Terminal (and Use It In Bash Scripts)



[ad_2]

Source link

You May Also Like

More From Author