Course outline

Bash / Advanced: channels, concurrency, and maintainable CLI design / Lesson 13

Control descriptors and redirection lifetimes

Treat redirection as ordered descriptor operations, separate data from diagnostics, and close explicitly owned dynamic descriptors.

About 4 min readContent reviewed
On this page

Lesson goals

  • Predict left-to-right duplication and redirection.
  • Distinguish command-scoped and exec-persistent descriptors.
  • Choose safe here-document and descriptor ownership contracts.

Descriptors identify open channels

Standard input, output, and error conventionally use descriptors zero, one, and two. A redirection changes where a descriptor points before the command executes. Duplicating a descriptor copies its current connection, not a promise to follow later redirections of the original number. This explains why >file 2>&1 differs from 2>&1 >file. Read the operations left to right and draw the connections when uncertain. A pipe is established within its own documented ordering relative to stage redirections, so do not infer behavior merely from visual proximity of symbols.

exec makes redirection persistent in the shell

A redirection attached to a simple command normally applies to that invocation. exec with redirections and no replacement command changes the current shell’s descriptors until they are restored or closed. A library helper that redirects the caller’s output can therefore have effects long after returning. Prefer a limited group or an explicitly owned extra descriptor for a narrow operation. Save any descriptor that must be restored before replacing it. Never assume a fixed high descriptor is unused; Bash supports dynamic allocation for supported versions, which avoids casual collision with application or shell internals.

bash Open and close a privately owned descriptor
lab=$(mktemp -d)
exec {out}>"$lab/note.txt"
printf '%s\n' 'synthetic note' >&"$out"
exec {out}>&-
cat -- "$lab/note.txt"
Expected output
synthetic note

The example creates only its new temporary file, writes through an allocated descriptor, closes it, and reads the synthetic content. No host configuration is accessed.

Choose expansion in here-documents deliberately

A quoted here-document delimiter suppresses normal expansion in its body, which is useful for literal templates containing dollar signs and backticks. An unquoted delimiter allows the documented expansions and can accidentally interpolate caller state into a generated file. <<- removes leading tab characters under its rules, not arbitrary indentation spaces. A here-string is a Bash feature and adds a newline, so it is not identical to supplying exact original bytes. Select the representation from the consumer’s contract and avoid turning a data template into a second shell program unintentionally.

Keep a stable diagnostic channel

A script that produces machine-readable output may need a separate progress channel. Duplicating the original standard output into an owned descriptor can preserve a connection while another group redirects its data. Alternatively, send diagnostics to standard error if that is the public contract. Do not merge the channels merely to simplify a pipeline unless the consumer understands the mixed format. An inherited extra descriptor can keep a pipe open and delay EOF, so close channels in children that do not need them. Descriptor lifetime is part of completion behavior, not only a formatting concern.

bash Verify separated channels in private fixtures
lab=$(mktemp -d)
{ printf 'data\n'; printf 'diagnostic\n' >&2; } >"$lab/out" 2>"$lab/err"
printf 'stdout=<%s> stderr=<%s>\n' "$(cat "$lab/out")" "$(cat "$lab/err")"
Expected output
stdout=<data> stderr=<diagnostic>

The group’s redirections are scoped to the group. Captured newline trimming is intentional for this displayed comparison of one-line synthetic fixtures.

Opening a channel can fail before work starts

The shell opens redirection targets before executing the command. A failed open can prevent the command from running, while > can truncate an existing file before a reader consumes it. Never transform a file by reading and redirecting back to the same pathname. Use a separate owned destination, validate required output, and publish through a reviewed operation appropriate to the filesystem and application. Check descriptor allocation and write failures explicitly when they affect correctness. Tests should include a controlled unavailable destination in a private fixture and confirm no partial artifact is accepted as complete.

Common errors and fixes

Both streams do not reach the expected file

Why it happens
Descriptor duplication occurred before the intended replacement.
How to fix it
Read redirections left to right and test channels separately.
Prevent it next time
Keep an explicit channel diagram for complex commands.

A helper permanently redirects its caller

Why it happens
exec changed the current shell without restoration.
How to fix it
Scope the redirection or restore an owned saved descriptor.
Prevent it next time
Document descriptor effects in library functions.

An input file becomes empty

Why it happens
Output redirection truncated the source before reading.
How to fix it
Use a distinct destination and preserve a source copy.
Prevent it next time
Compare source and destination paths before transformation.

Check your understanding

1. Explain 2>&1 >out using descriptor copying.

Show worked solution

Standard error first copies the current stdout connection. Standard output then moves to out, while stderr retains the earlier connection. If both should go to out, the file redirection must occur before stderr duplicates descriptor one.

2. Why can a pipe reader wait after the intended writer finishes?

Show worked solution

Another inherited write descriptor may still be open in a child or helper. EOF requires all relevant write ends to close. Track descriptor ownership and close unused inherited channels rather than treating a timeout as an arbitrary parser failure.

Key takeaways

  • Treat redirections as ordered connection operations.
  • Own, scope, and close descriptors explicitly.

Official references