Time and time again, I see Bash answers on Stack Overflow using eval
and the answers get bashed, pun intended, for the use of such an "evil" construct. Why is eval
so evil?
If eval
can't be used safely, what should I use instead?
Time and time again, I see Bash answers on Stack Overflow using eval
and the answers get bashed, pun intended, for the use of such an "evil" construct. Why is eval
so evil?
If eval
can't be used safely, what should I use instead?
There's more to this problem than meets the eye. We'll start with the obvious:
eval
has the potential to execute "dirty" data. Dirty data is any data that has not been rewritten as safe-for-use-in-situation-XYZ; in our case, it's any string that has not been formatted so as to be safe for evaluation.Sanitizing data appears easy at first glance. Assuming we're throwing around a list of options, bash already provides a great way to sanitize individual elements, and another way to sanitize the entire array as a single string:
Now say we want to add an option to redirect output as an argument to println. We could, of course, just redirect the output of println on each call, but for the sake of example, we're not going to do that. We'll need to use
eval
, since variables can't be used to redirect output.Looks good, right? Problem is, eval parses twice the command line (in any shell). On the first pass of parsing one layer of quoting is removed. With quotes removed, some variable content gets executed.
We can fix this by letting the variable expansion take place within the
eval
. All we have to do is single-quote everything, leaving the double-quotes where they are. One exception: we have to expand the redirection prior toeval
, so that has to stay outside of the quotes:This should work. It's also safe as long as
$1
inprintln
is never dirty.Now hold on just a moment: I use that same unquoted syntax that we used originally with
sudo
all of the time! Why does it work there, and not here? Why did we have to single-quote everything?sudo
is a bit more modern: it knows to enclose in quotes each argument that it receives, though that is an over-simplification.eval
simply concatenates everything.Unfortunately, there is no drop-in replacement for
eval
that treats arguments likesudo
does, aseval
is a shell built-in; this is important, as it takes on the environment and scope of the surrounding code when it executes, rather than creating a new stack and scope like a function does.eval Alternatives
Specific use cases often have viable alternatives to
eval
. Here's a handy list.command
represents what you would normally send toeval
; substitute in whatever you please.No-op
A simple colon in a no-op in bash: :
Create a sub-shell
Execute output of a command
Never rely on an external command. You should always be in control of the return value. Put these on their own lines:
Redirection based on variable
In calling code, map
&3
(or anything higher than&2
) to your target:If it were a one-time call, you wouldn't have to redirect the entire shell:
Within the function being called, redirect to
&3
:Variable indirection
Scenario:
Bad:
Why? If REF contains a double quote, this will break and open the code to exploits. It's possible to sanitize REF, but it's a waste of time when you have this:
That's right, bash has variable indirection built-in as of version 2. It gets a bit trickier than
eval
if you want to do something more complex:Regardless, the new method is more intuitive, though it might not seem that way to experienced programmed who are used to
eval
.Associative arrays
Associative arrays are implemented intrinsically in bash 4. One caveat: they must be created using
declare
.In older versions of bash, you can use variable indirection:
What about
or
?
How to make
eval
safeeval
can be safely used - but all of its arguments need to be quoted first. Here's how:This function which will do it for you:
Example usage:
Given some untrusted user input:
Construct a command to eval:
Eval it, with seemingly correct quoting:
Note you were hacked.
date
was executed rather than being printed literally.Instead with
token_quote()
:eval
isn't evil - it's just misunderstood :)