Linux Basic — eval, coprocess & exec
eval, coproc & exec
Three commands that look unrelated but share a theme: they let a script change how it executes itself. Each is powerful, each is easy to misuse. Reach for them deliberately.
eval — Re-Parse a String as a Command
eval takes a string, performs another full round of shell expansion, then runs the result.
cmd="ls -l /tmp"
eval "$cmd" # runs: ls -l /tmp
When you actually need it
Indirect variable access (pre-Bash-4.3, or portable to old Bash):
varname="my_value"
my_value="hello"
eval "echo \$$varname" # hello
Modern Bash has ${!varname} for this — prefer it.
Building a command with dynamic option lists:
opts=("--verbose" "--output=file.txt" "--format=json")
eval "myapp ${opts[@]}"
(But even here, myapp "${opts[@]}" without eval is usually correct and safer.)
The danger
eval executes its argument as code. If any part comes from untrusted input, you have a remote-code-execution bug:
# NEVER DO THIS
read -r user_input
eval "echo $user_input" # user types: $(rm -rf ~)
Rule of thumb: if you think you need eval, you probably need an array, ${!var}, or declare -n (nameref) instead.
coproc — Two-Way Pipe to a Background Process
Most pipelines are one-way. A coprocess lets your script send to and receive from a long-running helper.
coproc BC { bc -l; }
echo "scale=4; 22/7" >&"${BC[1]}"
read -r answer <&"${BC[0]}"
echo "pi ~ $answer"
exec {BC[1]}>&- # close write end
wait "$BC_PID"
BC[1] is the fd to the coprocess stdin; BC[0] is its stdout. $BC_PID is the PID.
Real uses
- Interactive helpers (
bc,gnuplot,dc,python -i) - Long-lived auth helpers (
gpg-agent-style) - Talking to a slow API client kept warm across many requests
For simple cases a mkfifo pair is more readable; coproc shines when you want it automatic.
exec — Replace the Current Shell, or Re-Wire its fds
exec has two modes:
1. Replace the current process
exec /usr/bin/python3 myscript.py
# nothing after this line ever runs
The shell becomes the new program — no fork, no return. Useful in wrappers that set env vars then hand off:
#!/usr/bin/env bash
export PATH="/opt/app/bin:$PATH"
exec myapp "$@"
2. Redirect fds for the whole script
Without a command, exec applies its redirections to the current shell:
exec > /var/log/myscript.log 2>&1
# every subsequent stdout/stderr in this script goes to the log
Open and close arbitrary file descriptors:
exec 3> output.txt # open fd 3 for writing
echo "hello" >&3
echo "world" >&3
exec 3>&- # close fd 3
This is the canonical pattern for logging an entire script without piping every line.
Quick Reference
| Tool | What it does | Use when |
|---|---|---|
eval |
Re-parse string as code | dynamic command (and you’ve ruled out arrays / namerefs) |
coproc |
Two-way pipe to background process | persistent helper you talk to repeatedly |
exec cmd |
Replace this shell with cmd |
wrapper scripts handing off to the real binary |
exec >file |
Redirect script’s fds | one-line script-wide logging |
Next → Practice Exercises