Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A Bash coprocess is an asynchronous background process spawned by the shell with a bidirectional inter-process communication (IPC) channel automatically established. Initiated using the coproc reserved word, it connects the standard input (stdin) and standard output (stdout) of the executed command to file descriptors accessible within the parent shell’s execution context.

Syntax

A coprocess can be declared anonymously (using the default name) or with an explicit identifier.

# Default anonymous coprocess
coproc command [args]


# Named coprocess using a compound command
coproc NAME { command [args]; }
Note: When declaring a named coprocess, a compound command (such as { ...; }) must be used. If a simple command is provided (e.g., coproc NAME echo "data"), Bash parses the intended NAME as the command itself and the subsequent words as its arguments.

State and File Descriptors

When a coprocess is invoked, Bash allocates a two-way pipe and populates specific variables in the parent shell to manage the IPC state. If no explicit NAME is provided, Bash defaults to the identifier COPROC.
  1. File Descriptor Array (NAME): Bash creates an indexed array containing two file descriptors.
    • ${NAME[0]}: Connected to the standard output of the coprocess. The parent shell reads from this descriptor.
    • ${NAME[1]}: Connected to the standard input of the coprocess. The parent shell writes to this descriptor.
  2. Process ID (NAME_PID): Bash stores the Process ID (PID) of the spawned background process in a variable suffixed with _PID.

I/O Operations and Asynchronous Hazards

Interaction with the coprocess relies on standard Bash file descriptor redirection. Writing to the coprocess: To send data to the coprocess, redirect standard output to the file descriptor stored in index 1.
echo "input data" >&"${NAME[1]}"
Reading from the coprocess: To retrieve data from the coprocess, redirect standard input from the file descriptor stored in index 0.
read -r response <&"${NAME[0]}"
The “Ambiguous Redirect” Hazard: Because the coprocess runs asynchronously, it may terminate before the parent shell executes an I/O operation. When a coprocess terminates, Bash automatically and immediately closes the file descriptors and unsets the NAME array. If the parent shell attempts to evaluate an I/O redirection like >&"${NAME[1]}" after the background process has finished and the array has been unset, the expansion yields an empty string. This randomly results in an ambiguous redirect error if the coprocess finishes faster than the parent shell expects. Robust implementations must verify the existence of the file descriptor or the PID before attempting I/O.

Lifecycle and Termination

The coprocess lifecycle is tied to the execution of its underlying command and the state of its file descriptors.
  • EOF Handling: To signal the coprocess that no more input will be sent, the parent shell must explicitly close the write file descriptor. This sends an End-Of-File (EOF) to the coprocess’s stdin. Because the {varname}>&- syntax requires a simple shell identifier and does not support array elements, the file descriptor must be closed dynamically. Critical Safety Pitfall: Due to the asynchronous cleanup mentioned above, if the coprocess terminates early, the NAME array is unset. If you use eval "exec ${NAME[1]}>&-", the empty expansion results in exec >&-, which silently closes the parent shell’s own standard output. This must be mitigated by using parameter expansion modifiers (like :?) or explicit checks:
# Method 1: Using eval with a strict safety check
eval "exec ${NAME[1]:?Coprocess already terminated}>&-"

# Method 2: Checking if the variable is set before assignment
if [[ -n "${NAME[1]:-}" ]]; then
    fd=${NAME[1]}
    exec {fd}>&-
fi
  • Automatic Cleanup: When the coprocess terminates, Bash automatically closes both file descriptors and unsets the NAME array and NAME_PID variable from the parent shell’s environment.
  • Concurrency Limit: Bash officially supports only one active coprocess at a time. Attempting to spawn a second coprocess before the first terminates will generate a warning (warning: execute_coproc: coproc [PID:NAME] already exists) and may result in orphaned file descriptors.

The Buffering Caveat

Because the IPC channel relies on standard pipes, the standard I/O streams of the command executing inside the coprocess are subject to block buffering by the C library (libc) when not connected to a pseudo-terminal (PTY). If the coprocess command does not explicitly flush its output buffers, the parent shell will hang indefinitely on a read operation, resulting in a deadlock. This mechanical limitation requires the internal command to either natively support line-buffering or be wrapped in utilities that manipulate stream buffering behavior (e.g., stdbuf -i0 -o0).
Master Bash with Deep Grasping Methodology!Learn More