eval / exec
| Since: | 全Linux | |
|---|---|---|
| macOS(2001 Cheetah) | ||
| Bash 1.0(1989) |
eval evaluates a string as a shell command and executes it. exec replaces the current shell process with another command, or redirects file descriptors. Both are powerful features, but misuse can introduce security vulnerabilities or cause a script to terminate unexpectedly — understanding how they work is essential before using them.
Syntax
eval concatenates its arguments into a single string and evaluates that string as a shell command.
eval [string ...]
When exec is given a command, the current shell process is replaced by that command — no new process is created.
exec command [arguments ...]
When exec is used with only a redirect (no command), it switches the standard I/O of the current shell without replacing the process.
exec >file 2>&1 exec <file
Comparing eval and exec
| Command | Behavior | Process | Typical Use |
|---|---|---|---|
| eval | Evaluates and executes a string as a command | Runs in the current shell (no subshell) | Dynamic variable names, assembling command strings |
| exec command | Replaces the current shell with another command | Overwrites the process without forking | Handing off to another command at the end of a script |
| exec redirect | Switches file descriptors of the current shell | Process unchanged | Redirecting all script output to a log file |
Sample Code
Using eval to reference dynamic variable names. A variable name is built as a string, and eval retrieves the value stored in that variable.
time_leap.sh (excerpt)
#!/bin/bash
# Dynamically reference messages for each worldline
msg_alpha="Alpha worldline: the road to Steins;Gate"
msg_beta="Beta worldline: a different fate"
msg_steinsgate="Steins;Gate worldline: objective reached"
for worldline in alpha beta steinsgate; do
eval "echo \$msg_${worldline}"
done
bash time_leap.sh Alpha worldline: the road to Steins;Gate Beta worldline: a different fate Steins;Gate worldline: objective reached
Using eval to assemble and run a command string dynamically, varying the command based on conditions.
gadget_launcher.sh (excerpt)
#!/bin/bash
# Dynamically launch a future gadget
gadget_no=8
action="send"
target="okabe@example.com"
# Assemble the command string dynamically
cmd="mail -s 'Future Gadget No.${gadget_no} activated' ${target}"
echo "Command to run: $cmd"
eval "$cmd <<< 'D-Mail: operation complete'"
Using exec to redirect all output from a script to a log file. Every command after the exec redirect writes its stdout and stderr to the file.
reading_steiner.sh
#!/bin/bash # Record all script output to a log file LOG_FILE="reading_steiner.log" # Redirect all stdout and stderr to the log file from this point on exec >"$LOG_FILE" 2>&1 echo "Operation start: $(date)" echo "Worldline divergence: 0.571024%" # Some processing ping -c 1 example.com echo "Operation end: $(date)"
bash reading_steiner.sh cat reading_steiner.log Operation start: Thu Apr 9 10:00:00 UTC 2026 Worldline divergence: 0.571024% PING example.com (93.184.216.34): 56 data bytes 64 bytes from 93.184.216.34: icmp_seq=0 ttl=56 time=150.3 ms Operation end: Thu Apr 9 10:00:01 UTC 2026
Using exec to replace the current process with another command. No code after the exec line is executed.
worldline_shift.sh
#!/bin/bash # Replace the current shell process with /bin/bash echo "Before replacement: PID=$$" # The current shell is replaced by /bin/bash # Nothing after this exec line will run exec /bin/bash --login echo "This line never executes"
bash worldline_shift.sh Before replacement: PID=12345 bash-5.2$
Overview
eval performs double evaluation. The shell first expands variables, command substitutions, and other expansions to produce a final string, then evaluates that string again as a command. This two-pass evaluation makes it possible to write dynamic code that cannot be expressed with normal syntax.
When exec command is run, the current shell process (PID) is overwritten by the new command. No fork (child process creation) occurs. Using exec at the end of a script hands off to another program without consuming an extra shell process.
exec redirect (without a command) does not replace the process — it only switches the file descriptors (FDs) of the current shell. Writing exec >logfile 2>&1 at the top of a script sends all subsequent output to the log file.
Security note: never pass user input to eval
Passing external input — user input, file contents, or environment variables — directly to eval creates a command injection vulnerability that allows arbitrary code execution. This is one of the most dangerous patterns in Bash scripting.
gadget_launcher_ng.sh
#!/bin/bash
# Dangerous: passing user input directly to eval
read -p "Enter your name: " user_input
eval "echo Hello, ${user_input}"
If a malicious string is entered, arbitrary commands are executed.
bash gadget_launcher_ng.sh Enter your name: Okabe Rintaro; rm -rf /tmp/important_data; echo hacked Hello, Okabe Rintaro hacked
The semicolons cause eval to run rm -rf /tmp/important_data and echo hacked as separate commands. When handling user input, avoid eval and use arrays or safe string processing.
gadget_launcher_ok.sh
#!/bin/bash
# Safe: build the command with an array instead of eval
cmd_args=("echo" "Hello, ${user_input}")
"${cmd_args[@]}"
Common Mistakes
Common Mistake 1: eval injection vulnerability
The danger of eval extends beyond direct user input — script arguments ($1) and environment variables passed to eval are equally risky.
check_var_ng.sh
#!/bin/bash # Dangerous: passing a script argument directly to eval target_var=$1 eval "echo \$$target_var"
bash check_var_ng.sh 'PATH; cat /etc/passwd' /usr/local/bin:/usr/bin:/bin root:x:0:0:root:/root:/bin/bash ...
Passing a string that contains ; causes commands unrelated to variable lookup to be executed. When using a value as a variable name, validate that it contains only alphanumeric characters and underscores.
check_var_ok.sh
#!/bin/bash
# Safe: validate the variable name before using eval
target_var=$1
# Allow only characters valid in a variable name (letters, digits, underscore)
if [[ ! "$target_var" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
echo "Error: invalid variable name: $target_var" >&2
exit 1
fi
eval "echo \$$target_var"
Common Mistake 2: exec is a one-way door — the script never returns
Once exec replaces the process, the current shell is gone. No code after the exec line runs, and there is no way to return to the original script.
worldline_shift.sh
#!/bin/bash
# Code after exec never runs
echo "Starting process"
echo "Recording log"
exec /usr/bin/python3 -c "print('Switched to Python')"
# The following lines never execute
echo "This line never executes"
cleanup_function
bash worldline_shift.sh Starting process Recording log Switched to Python
Writing cleanup code after exec with the intention of running it later silently skips that code, potentially causing resource leaks. One approach is to run cleanup before exec, or to combine it with trap (though trap handlers do not fire after a process replacement).
A exec redirect (no command) does not replace the process, so the script continues normally.
fd_redirect.sh
#!/bin/bash # exec redirect does not replace the process exec >"reading_steiner.log" 2>&1 # FD switch only echo "This line executes (written to the log file)" echo "The script continues"
If you find any errors or copyright issues, please contact us.