Linux Basic — eval, coprocess & exec

2 minute read

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

Updated: