Advanced logging, locking and two-way communication all rely on managing descriptors yourself.
Redirections on exec without a command apply to the current shell for the rest of the script:
exec 3> fileopens fd 3 for writing,exec 3< filefor reading,exec 3<> fileboth,exec 3>&-closes it,exec {fd}> filelets bash choose a free number (10 or above) and stores it infd; use>&"$fd".
Duplication n>&m makes fd n a copy of fd m at that moment, which explains classic tricks. cmd 2>&1 >/dev/null sends only stderr into a pipe and discards stdout. 3>&1 1>&2 2>&3 3>&- swaps stdout and stderr through a spare descriptor.
exec > >(tee -a run.log) 2>&1 near the top of a script copies all following output to a log file while still showing it. Because the tee runs asynchronously, its last lines can appear after the script exits.
Descriptors also power locking: exec 9> /var/lock/job.lock; flock -n 9 || exit 1 ensures only one instance runs, and the lock is released automatically when the process exits.
coproc NAME { cmd; } starts a background process with two pipes; write to ${NAME[1]} and read from ${NAME[0]}. Watch for buffering: many programs only flush output when their input closes. Inspect a process's descriptors in /proc/$$/fd.
Example
cd "$(mktemp -d)"
exec 3> log.txt # fd 3 stays open for the rest of the script
echo "to the log" >&3
exec 3>&- # close it
cat log.txt # to the log
exec {fd}< /etc/passwd # bash picks a free fd number
read -r first <&"$fd"; exec {fd}<&-
echo "${first%%:*}" # root
{ echo out; echo err >&2; } 2>&1 >/dev/null | tr a-z A-Z # ERR
{ echo out; echo err >&2; } 3>&1 1>&2 2>&3 3>&- | cat # err via the pipe, out on stderr
exec 9> job.lock
flock -n 9 && echo "got the lock" # got the lock
coproc UP { tr a-z A-Z; }
echo "hello coproc" >&"${UP[1]}"; exec {UP[1]}>&-
read -r line <&"${UP[0]}"; echo "$line" # HELLO COPROC
ls /proc/$$/fd # 0 1 2 9 255 ... (open descriptors)Remember
- exec with only redirections changes the current shell's descriptors
- n>&m copies m at that moment; order explains 2>&1 >/dev/null
- flock on an fd gives easy single-instance locking
Watch out for
- Expecting
cmd >/dev/null 2>&1andcmd 2>&1 >/dev/nullto be the same.