Skip to content
Shell & TerminalDeep Dive Published Updated 3 min readViews unavailable

Shell File Descriptors and Redirection: Ordering, Duplication, and Lifetime

A precise model of shell file descriptors, left-to-right redirection, duplication versus paths, grouping, pipelines, persistent descriptors, and safe logging.

Shell redirection changes a process’s table of open file descriptors before or around command execution. The critical rules are that redirections are processed in written order and that 2>&1 duplicates the current descriptor 1—it does not create a permanent symbolic link to “where stdout eventually goes.” Those two facts explain most surprising logging behavior.

Descriptor numbers name open handles

By convention, 0 is standard input, 1 standard output, and 2 standard error. A process may have many more descriptors for files, pipes, sockets, terminals, and directories. Shell syntax can open, duplicate, move in some shells, or close entries in that table.

Consider two commands:

build >build.log 2>&1
build 2>&1 >build.log

In the first, descriptor 1 is opened on build.log, then descriptor 2 is duplicated from the updated 1; both go to the file. In the second, descriptor 2 is first duplicated from the shell’s original 1, then only descriptor 1 changes to the file. Errors remain on the original output destination.

A path reopening is different from duplication. cmd >file 2>file opens the same pathname twice, potentially with independent file offsets and truncation rules. cmd >file 2>&1 opens once and shares the underlying open file description, producing more predictable interleaving—though writes from concurrent processes can still mix.

Redirection scope follows shell syntax

A redirection after a compound command applies to the whole group:

{
    prepare
    compile
    package
} >build.log 2>&1

Braces run in the current shell and require separators around their syntax. Parentheses create a subshell in POSIX shell. State changes inside ( cd dir; work ) do not affect the parent, which makes directory-scoped execution safer.

In a pipeline, each command gets pipe endpoints plus its own redirections. producer 2>&1 | consumer sends both streams into the pipe. producer 2>errors | consumer keeps errors separate. Pipeline exit status normally reflects the last command in POSIX shell; options such as pipefail are extensions whose availability and exact behavior must be checked.

Persistent descriptors need lifecycle discipline

exec without a command applies redirections to the current shell:

exec 3>>audit.log
printf '%s\n' "starting" >&3
# ...
exec 3>&-

Closing descriptor 3 prevents accidental inheritance by later children. Select descriptor numbers carefully; POSIX applications can rely on only a limited range, and shells reserve some descriptors internally. Advanced Bash syntax can allocate descriptors dynamically, but it is not portable POSIX syntax.

Opening a descriptor before dropping privilege can intentionally give a child narrow access to one resource, but inherited descriptors are also a security risk. Close secrets, sockets, and lock files in children that do not need them. Redirection does not bypass the permissions checked when the shell opens the path.

Avoid data-loss shortcuts

> truncates the output file before the command runs. A misspelled input/output path can erase the source, as in filter <data >data. Write transformations to a temporary file in the same directory, verify success, then atomically rename. Use >> only when an append-only log is truly intended, and add rotation and locking appropriate to multiple writers.

Test commands with distinguishable stdout/stderr lines, pipeline failures, grouped commands, and unwritable destinations. Once redirection is viewed as a left-to-right series of descriptor-table operations, its behavior becomes deterministic instead of punctuation folklore.

Related:

Sources:

Comments