There are Bash commands that are simple, useful, and almost impossible to misuse.
And then there is eval.
eval is one of those commands that looks almost magical when you first discover it. You give it a string containing Bash code, and somehow that string becomes actual shell code and gets executed.
For example:
eval 'echo "Hello from eval"'
produces:
Hello from eval
That sounds simple enough.
But eval is doing something much more interesting than simply "running a string."
It tells Bash to take its arguments, construct a command from them, and parse that command again as shell code.
That second round of parsing is the important part.
It is also the reason eval can be extremely powerful, and extremely dangerous.
Let's take a closer look.
What is eval?
eval is a Bash builtin that takes its arguments, combines them into a command string, and asks the shell to execute that resulting command.
The simplest example is:
eval 'echo "Hello"'
Bash receives the argument:
echo "Hello"
and then evaluates it as shell code.
The result is:
Hello
You can check that eval is a Bash builtin with:
$ type eval
eval is a shell builtin
And, as with other Bash builtins, you can read its built-in documentation:
$ help eval
The important word here is evaluate.
eval doesn't treat its arguments as ordinary text. It takes the resulting text and gives it back to the shell parser.
The basic idea
Consider this:
command='echo Hello'
echo "$command"
This prints:
echo Hello
The contents of the variable are treated as data.
Now compare it with:
command='echo Hello'
eval "$command"
This prints:
Hello
Why?
Because the first version says:
Print the contents of command.The second says:
Take the contents of command and interpret them as Bash code.That distinction is the entire reason eval exists.
eval does not simply "execute a string"
It is tempting to describe eval as:
"Execute the contents of a string."
That description is useful for beginners, but technically incomplete.
What actually happens is closer to:
Arguments
↓
Normal shell expansion
↓
eval receives the resulting text
↓
Arguments are combined
↓
Bash parses the resulting text again
↓
The resulting commands are executed
That second parsing step is what makes eval special.
It means that characters which were previously just part of a string can become shell syntax during the second evaluation.
For example:
command='echo Hello; echo World'
eval "$command"
produces:
Hello
World
The semicolon was stored inside the variable as ordinary text.
During the second parsing pass, however, Bash recognizes it as a command separator.
A simple example
Let's start with something harmless:
command='echo "Hello from a variable"'
eval "$command"
Output:
Hello from a variable
Without eval:
echo "$command"
the output would simply be:
echo "Hello from a variable"
So:
echo "$command"
prints the text.
While:
eval "$command"
interprets the text as shell code.
Why would anyone need this?
At first glance, eval may seem unnecessary.
If you already have a command:
echo "Hello"
why would you put it into a variable?
Why not just execute it directly?
That's a very good question.
In many cases, you shouldn't use eval at all.
But there are situations where a program needs to construct shell syntax dynamically.
The classic example, and the one from the original article, is dynamically creating variable names.
Dynamic variable names
Suppose we have:
i=1
and want to create a variable called:
var_1
with the value:
Dynamic Variable Name
We could write:
var_1="Dynamic Variable Name"
But what if the number is stored in a variable?
i=1
and we want the variable name to be:
var_1
We could construct the text:
echo "var_${i}"
which produces:
var_1
But that only creates a string.
We need Bash to interpret that string as an assignment.
This is where the traditional eval example comes in:
i=1
eval "var_${i}=\"Dynamic Variable Name\""
echo "$var_1"
Output:
Dynamic Variable Name
The command constructed for eval is effectively:
var_1="Dynamic Variable Name"
Bash then evaluates that command and creates the variable.
Breaking the example down
Let's make the process visible.
Start with:
i=1
Now:
eval "var_${i}=\"Dynamic Variable Name\""
The outer double quotes allow Bash to expand:
${i}
so the text passed to eval becomes approximately:
var_1="Dynamic Variable Name"
eval then asks Bash to parse that text as shell code.
The shell sees:
var_1="Dynamic Variable Name"
which is a normal variable assignment.
The result is:
echo "$var_1"
producing:
Dynamic Variable Name
This is the classic use case for eval.
Multiple dynamic variables
You could extend the same idea:
for i in 1 2 3; do
eval "var_${i}=\"Value ${i}\""
done
echo "$var_1"
echo "$var_2"
echo "$var_3"
Output:
Value 1
Value 2
Value 3
The loop dynamically creates:
var_1
var_2
var_3
This demonstrates why eval can look very attractive.
You can construct shell syntax dynamically and let Bash execute it.
But there is a much better modern solution for this particular problem.
Bash arrays are usually better
If you want a collection of values indexed by a number, Bash already provides arrays.
Instead of creating:
var_1
var_2
var_3
you can use:
values[1]="Value 1"
values[2]="Value 2"
values[3]="Value 3"
and access them with:
echo "${values[1]}"
For example:
values=()
for i in 1 2 3; do
values[$i]="Value $i"
done
echo "${values[1]}"
echo "${values[2]}"
echo "${values[3]}"
Output:
Value 1
Value 2
Value 3
This is easier to read and doesn't require eval.
So although dynamic variable names are a classic reason to use eval, in modern Bash an array is often the better solution.
Associative arrays are even better for names
Bash also supports associative arrays.
Suppose you want values identified by names:
server_name="production"
server_ip="192.168.1.100"
Instead of dynamically constructing variables such as:
server_production
server_staging
server_testing
you can use:
declare -A servers
servers[production]="192.168.1.100"
servers[staging]="192.168.1.101"
servers[testing]="192.168.1.102"
Then:
environment="production"
echo "${servers[$environment]}"
produces:
192.168.1.100
This is usually much cleaner than dynamically generating variable names with eval.
The dangerous side of eval
Now we reach the part that makes experienced shell programmers nervous.
Consider:
name="$1"
eval "echo Hello $name"
At first glance, it looks harmless.
Perhaps you expect:
./script.sh Pavel
to produce:
Hello Pavel
And it does.
But what happens if someone supplies:
Pavel; echo HACKED
The constructed command becomes:
echo Hello Pavel; echo HACKED
And eval executes both commands.
The output becomes:
Hello Pavel
HACKED
The semicolon was not supposed to be data.
But eval turned it into shell syntax.
The problem is a second round of parsing
This is the fundamental danger.
Suppose:
user_input='hello; echo BAD'
and:
eval "echo $user_input"
The first stage produces text equivalent to:
echo hello; echo BAD
Then eval parses that text again.
The semicolon now has its normal shell meaning:
command 1 ; command 2
So both commands execute.
Without eval:
echo "$user_input"
you simply get:
hello; echo BAD
The semicolon remains data.
This is why passing untrusted input to eval is dangerous.
A more serious example
Imagine a script that receives a filename:
filename="$1"
eval "cat $filename"
The programmer may have intended:
./script.sh report.txt
to execute:
cat report.txt
But a malicious argument could contain shell syntax.
For example:
report.txt; rm -rf something
could turn into:
cat report.txt; rm -rf something
The exact consequences depend on the input and environment, but the underlying problem is clear:
The data has become executable shell syntax.
This is why eval should never be casually used with untrusted input.
Quoting does not magically make eval safe
You may think:
"Fine. I'll just quote the variable."
For example:
eval "echo \"$name\""
This can solve some specific quoting problems, but it doesn't make the general design safe.
The problem with eval is that you're deliberately asking Bash to parse generated text as shell code.
Every expansion, quote, command substitution, redirection, wildcard, semicolon, pipe, and other shell operator in that generated text potentially becomes meaningful during the second parsing stage.
The more complicated the generated command becomes, the harder it is to reason about safely.
That is one of the main reasons to avoid eval unless you genuinely need it.
Prefer arrays for dynamically constructed commands
A very common reason people reach for eval is constructing commands dynamically.
For example, someone might try:
command="ls -l $directory"
eval "$command"
A much safer Bash technique is to use an array:
command=(ls -l "$directory")
"${command[@]}"
Now each command argument remains a separate element.
For example:
directory="/some directory"
command=(ls -l "$directory")
"${command[@]}"
The directory containing a space is still passed as one argument.
There is no second shell parsing pass.
This is a major advantage.
Building commands with arrays
Suppose optional arguments need to be added:
command=(tar -czf backup.tar.gz)
if [[ $verbose == true ]]; then
command+=(-v)
fi
command+=("$directory")
"${command[@]}"
This is much easier to reason about than constructing a giant string and feeding it to eval.
With an array:
element 1 → tar
element 2 → -czf
element 3 → backup.tar.gz
element 4 → directory
The shell knows exactly what each argument is.
With eval, Bash has to parse the entire generated string again.
eval and shell functions
Another situation where eval can appear is when people dynamically construct function calls.
For example:
function_name="start_server"
eval "$function_name"
This can work.
But Bash already provides better mechanisms.
You can simply use:
"$function_name"
when the function name is stored in a variable and corresponds to a valid shell function.
For example:
start_server() {
echo "Server started"
}
function_name="start_server"
"$function_name"
Output:
Server started
There is no reason to introduce eval here.
Running a command stored in a variable
This is another common trap.
Someone writes:
command="echo Hello World"
$command
and it works:
Hello World
Then they encounter a more complicated command and decide:
eval "$command"
is the solution.
Usually, it isn't.
If you're constructing a command programmatically, arrays are generally a better approach:
command=(echo Hello World)
"${command[@]}"
For external commands with arguments:
command=(grep -i "hello" "$file")
"${command[@]}"
This preserves argument boundaries instead of creating a string that must later be reparsed.
When eval can actually be useful
Despite all the warnings, eval isn't inherently evil.
There are legitimate cases where dynamically generated shell syntax is genuinely required.
One example is interacting with shell syntax that cannot easily be represented as ordinary data.
The classic example is dynamic variable assignment from older shell programming techniques:
i=1
eval "var_${i}=\"Dynamic value\""
Another situation can occur in advanced shell metaprogramming, where a program intentionally generates Bash source code and then asks Bash to interpret it.
But this should be considered an advanced technique.
If the problem can be solved with:
- arrays
- associative arrays
- indirect variable expansion
- functions
case- normal parameter expansion
- command arguments
then those approaches are generally easier to understand and safer.
Modern Bash has indirect expansion
There is an especially interesting alternative to eval for dynamic variable access.
Suppose:
var_1="Hello"
name="var_1"
You want to retrieve the value of the variable whose name is stored in $name.
In modern Bash, you can use indirect expansion:
echo "${!name}"
Output:
Hello
Here:
name="var_1"
contains the name of another variable.
The ! tells Bash to perform an indirect expansion.
So:
${!name}
means approximately:
Use the value of name as the name of another variable, then retrieve that variable's value.This can eliminate many historical uses of eval.
For example:
var_1="one"
var_2="two"
var_3="three"
for i in 1 2 3; do
name="var_$i"
echo "${!name}"
done
Output:
one
two
three
No eval required.
declare can also help
Bash's declare builtin provides another way to work with variables dynamically.
For example:
name="my_variable"
value="Hello"
declare "$name=$value"
Now:
echo "$my_variable"
produces:
Hello
For more advanced scripts, tools such as declare, arrays, associative arrays, and indirect expansion can often replace what older Bash scripts accomplished with eval.
eval and command substitution
Remember that Bash performs command substitution using $():
echo "$(date)"
This is different from eval.
Command substitution means:
Execute this command and substitute its output here.
eval means:
Take this resulting text and parse it as shell code.
For example:
command='date'
echo "$command"
prints:
date
while:
eval "$command"
actually runs:
date
These are very different operations.
A useful way to think about eval
The easiest mental model is:
Without eval
command='echo Hello'
echo "$command"
The contents are data.
echo Hello
With eval
command='echo Hello'
eval "$command"
The contents become code.
echo Hello
The string crosses the boundary between data and executable shell syntax.
That is the power of `eval.
And that boundary is exactly where the danger lives.
A practical example
Imagine a script that needs to choose an action dynamically.
A bad approach might be:
action="$1"
eval "$action"
This means the user can execute arbitrary shell code.
A much better approach is to explicitly choose what is allowed:
case "$action" in
start)
start_server
;;
stop)
stop_server
;;
restart)
restart_server
;;
*)
echo "Unknown action: $action"
exit 1
;;
esac
Now the input is treated as data and compared against known values.
This is one of the recurring principles of shell programming:
Don't turn data into code when you can simply treat it as data.
A small checklist before using eval
Before writing:
eval ...
stop for a moment and ask:
- Am I trying to access a variable dynamically?
- Could an array solve the problem?
- Could an associative array solve it?
- Could indirect expansion solve it?
- Could a function solve it?
- Could
caseselect the required operation? - Am I constructing a command that could instead be represented as an array?
- Does any part of the generated text come from user input or another untrusted source?
If the answer to the last question is yes, be extremely cautious.
In many cases, the correct solution is simply:
Don't use eval.
Debugging eval
When working with an existing script that uses eval, it can be useful to see exactly what command is being generated.
For example:
command="echo Hello"
printf 'Generated command: %s\n' "$command"
eval "$command"
Output:
Generated command: echo Hello
Hello
This makes the second evaluation step visible.
For more complicated scripts, Bash tracing can also help:
bash -x script.sh
or:
set -x
When debugging eval, always try to determine:
What exact string reaches the second parsing stage?
That is usually where the problem becomes obvious.
eval in one example
Let's compare three approaches.
1. Treat the string as data
command='echo Hello'
echo "$command"
Result:
echo Hello
2. Execute a command directly
echo Hello
Result:
Hello
3. Generate shell code and evaluate it
command='echo Hello'
eval "$command"
Result:
Hello
The third approach is the most powerful—and therefore the one that deserves the most suspicion.
Conclusion
eval is a small Bash builtin with a surprisingly large amount of power.
Its basic purpose is:
Take its arguments, construct a command from them, and have Bash evaluate that command as shell code.
The classic example is:
i=1
eval "var_${i}=\"Dynamic Variable Name\""
echo "$var_1"
which dynamically creates var_1.
But modern Bash provides many alternatives that are usually preferable:
- arrays
- associative arrays
- indirect variable expansion
declare- functions
case- command arrays
The most important thing to remember is that eval causes another round of shell parsing.
That means data can become shell syntax.
For example:
input='hello; echo HACKED'
eval "echo $input"
does not merely print the input. The semicolon becomes a command separator during the second parsing pass.
That's why eval should be treated as an advanced tool rather than a general-purpose way to "run a string."
A good rule of thumb is:
If you can solve the problem without eval, do so.
And if you genuinely need it, make sure you understand exactly what string Bash will parse—and exactly where every character in that string came from.
eval isn't a monster hiding under your bed.
It's worse.
It's a perfectly legitimate Bash feature that will happily do exactly what you asked, even when what you asked was a terrible idea.