Shell builtins · bash builtin
eval
Join the arguments into a string and run it as shell code, with a second round of parsing and expansion.
eval [ARGUMENT...]
eval takes its arguments, joins them with spaces, and hands the result to the shell's
parser as if you had typed it. The text is therefore expanded twice: once when the shell
builds eval's arguments, and again when eval runs them. That second pass is the whole
point: it can turn data into variable names, quotes, pipes and redirections.
It is also why eval is dangerous. Anything inside the evaluated string that comes from a
user, a file name, an HTTP response or an environment variable is executed as code. A value
such as x; rm -rf ~ is harmless in a variable and catastrophic inside eval.
Most historic uses of eval have safer replacements in modern bash: arrays for building
commands, ${!name} and declare -n for indirect variables, printf -v for assigning to a
variable whose name is in another variable, and printf %q when you truly must produce shell
code. Legitimate uses remain, mostly for loading code a trusted program prints for you, such
as eval "$(ssh-agent -s)".
Examples
Run a command stored in a string
cmd='grep -c ERROR logs/app.log'
eval "$cmd"
2
eval parses the string as a full command line. Plain $cmd would also work for this
simple case, but not for anything with quotes, pipes or redirections, as the next example
shows.
Pipes and redirections in a string need eval
cmd='sort data/fruits.txt | uniq -c | head -n 3'
$cmd
echo "---"
eval "$cmd"
sort: extra operand '|' not allowed with -c
---
3 apple
2 banana
2 cherry Expanding $cmd only splits it into words, so sort receives |, uniq, -c and the
rest as arguments, takes -c as its "check" option and fails. eval reparses the text and the | becomes a real pipe. This is the kind
of power that makes it risky.
Why eval is dangerous
#!/usr/bin/env bash
name=$1
eval "echo Hello, $name"
bash greet-unsafe.sh 'Alice'
bash greet-unsafe.sh 'Alice; touch HACKED; ls H*'
Hello, Alice Hello, Alice HACKED
The second argument is ordinary data, but eval treats the ; in it as a command
separator, so the "name" creates a file and lists it. With a less friendly payload this
would delete files or leak secrets. Never pass untrusted text to eval.
File names can carry code too
Setup run before this example
touch 'report$(echo INJECTED >&2).txt'for f in report*; do
eval "echo processing $f"
done
INJECTED processing report.txt
The file name contains $(...). The for loop handles it as plain text, but eval
expands it a second time and runs the command inside, printing INJECTED. Anyone who can
create a file in a directory your script walks can run code as you.
Build commands with an array instead
args=(-c -i)
pattern='error'
args+=(-- "$pattern" "logs/app.log")
grep "${args[@]}"
2
An array keeps every argument separate, spaces and special characters included, with no second parse. This replaces almost every "build a command string then eval it" pattern.
Read a variable whose name is in another variable
db_host=db01; web_host=web01
role=db
var="${role}_host"
echo "unsafe: $(eval "echo \$$var")"
echo "safe: ${!var}"
unsafe: db01 safe: db01
${!var} expands the variable named by var without running any code. The eval
version gives the same result but would execute whatever var contained.
Assign to a variable named at run time
set_config() { printf -v "cfg_$1" '%s' "$2"; }
set_config port 8080
set_config motd 'hello; rm -rf /'
declare -p cfg_port cfg_motd
declare -- cfg_port="8080" declare -- cfg_motd="hello; rm -rf /"
printf -v NAME assigns to a variable by name and treats the value as plain data. The
equivalent eval "cfg_$1=$2" would have run rm -rf /. bash refuses a NAME that is not
a valid identifier, so it cannot be abused either. declare -n is the other option.
Quote safely with %q when you must generate code
name='Alice; touch HACKED'
eval "echo Hello, $(printf '%q' "$name")"
ls HACKED
Hello, Alice; touch HACKED ls: cannot access 'HACKED': No such file or directory
printf %q escapes the value so the second parse reads it back as one literal word. The
; is printed instead of executed, and ls confirms that no file was created.
Load shell code printed by a trusted program
gen() { echo 'export BUILD_ID=42 BUILD_ENV=ci'; }
eval "$(gen)"
env | grep '^BUILD_'
BUILD_ENV=ci BUILD_ID=42
Tools such as ssh-agent -s, dircolors and pyenv init - print shell assignments
meant to be evaluated in the current shell. This is the accepted use of eval: the code
comes from a program you trust, not from data. Always quote "$(...)".
Watch the two rounds of expansion
x='$HOME'
echo $x
eval echo $x
$HOME /home/dev
The first echo prints the value of x as is, $HOME. With eval, the result of the
first expansion is expanded again, and $HOME becomes the home directory.
Common pitfalls
Passing anything that contains user input, file names, or downloaded text to
eval. It runs;,$(...), backticks and redirections inside the data.Unquoted
eval $cmdjoins and re-splits the words, mangling spacing and globbing them. If you must use it, writeeval "$cmd".Reaching for
evalto get indirect variables:${!name},declare -nandprintf -vdo it without executing code.Building a command line as a string and evaluating it; use an array (
cmd=(grep -r -- "$pat" .); "${cmd[@]}").
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
evalis POSIX and behaves the same in every shell. The safer replacements are not:${!var},declare -n,printf -vandprintf %qare bash features (zsh has its own${(P)var}).
Related
declareCreate variables with attributes (array, associative array, integer, read-only, exported), or show how variables are defined.printfPrint text and values through a format string, with precise control over spacing, numbers and escapes.sourceRun the commands in a file inside the current shell, so its variables, functions and directory changes stay in effect.execReplace the shell with another program, or, with only redirections, change where the shell's own input and output go.setTurn shell options such as -e, -u, -x and pipefail on or off, and replace the positional parameters.- QuotingSingle quotes, double quotes, backslashes and $'...', what each protects, and the unquoted-variable bugs they prevent, each shown failing and then fixed.
- ArraysIndexed arrays in bash, creating, reading, appending, looping, slicing and removing elements, and using them to build command lines safely.
- Command substitutionCapturing a command's output with $(...), what happens to trailing newlines and stderr, how to check the exit status, and the local trap that hides failures.
- Bash idioms for real scriptsThe small patterns that make scripts robust - die(), logging to stderr, ${1:?usage}, command -v checks, mktemp with trap cleanup, retries with backoff, locking and safe config parsing.