Redirection changes a command’s file descriptors before the command executes. The shell processes redirections from left to right, opening or duplicating a descriptor as it reaches each operation. Descriptors 0, 1, and 2 conventionally represent standard input, standard output, and standard error. Reading this sequence as concrete changes to those handles explains both misplaced diagnostics and files that become empty before their contents can be read.

Opening a file is already a side effect

>output opens a file for writing and normally truncates an existing regular file. >>output opens it for appending. <input opens a file for reading. These actions belong to the shell, not to the program named by the command. If opening a required target fails, the command is not executed; successful earlier redirections can still have created or truncated other files.

Consequently, transforming a file with sed expression input >input destroys the original before sed can read it. Opening input first with <input does not rescue the data if a later redirection truncates the same underlying file. Write to a separate temporary file, check the transformation’s status and result, and only then perform a deliberate replacement appropriate to the file’s ownership and filesystem.

Duplication captures the current destination

>log 2>&1 first sends descriptor 1 to the log, then makes descriptor 2 refer to that same open destination. 2>&1 >log first duplicates the original standard output onto descriptor 2, then changes only descriptor 1. Standard error therefore remains at the original destination, such as a terminal or a capturing pipe. Duplication copies a descriptor reference, not file contents, and it does not create a live connection between descriptor numbers.

Bash’s &>log is shorthand for redirecting both streams, but >log 2>&1 also works in portable shell scripts. Opening the same filename twice with >log 2>log is not equivalent: the two opens have independent offsets and can overwrite one another’s output. Even when descriptors share one destination, application buffering can affect the order in which messages become visible.

Pipelines connect standard output to the next stage before command-specific redirections are applied. In producer >file | consumer, the producer’s output consequently goes to the file, leaving the consumer without that data. Conversely, producer 2>&1 | consumer sends diagnostics through the pipe too. When the consumer expects machine-readable records, merging human-readable errors into that stream can corrupt otherwise valid input.

When it breaks

Quote variable filenames so spaces do not turn a target into multiple words. Keep the descriptor number adjacent to its operator: 2>errors redirects standard error, while 2 >errors supplies an ordinary argument containing 2 and redirects standard output. A misplaced space can therefore change both program input and output routing.

Bash’s set -C enables noclobber, rejecting ordinary output redirection to an existing regular file. It is useful protection against accidental replacement, not a backup system or a universal guarantee about every file type. >| explicitly overrides it. Also distinguish a command’s temporary redirections from exec >log, which changes the current shell’s descriptors and can redirect every subsequent command. Local tests should not unexpectedly silence the surrounding session.

Check it quickly

Run the following in Bash with standard Unix utilities. Only freshly created temporary files are truncated or removed. The output compares both orderings, demonstrates same-file truncation, and checks that a failed redirection prevents a function body from running.

bash --noprofile --norc <<'BASH'
set -eu
demo_dir=$(mktemp -d)
trap 'rm -f "$demo_dir/both" "$demo_dir/out" "$demo_dir/input"; rmdir "$demo_dir"' EXIT
emit() { printf 'out\n'; printf 'err\n' >&2; }
emit > "$demo_dir/both" 2>&1
demo_captured=$(emit 2>&1 > "$demo_dir/out")
printf 'both:\n'
cat "$demo_dir/both"
printf 'out-file=[%s] captured=[%s]\n' \
  "$(cat "$demo_dir/out")" "$demo_captured"
printf 'seed\n' > "$demo_dir/input"
sed 's/seed/replaced/' "$demo_dir/input" > "$demo_dir/input"
if [ ! -s "$demo_dir/input" ]; then printf 'input truncated\n'; fi
if (set -C; emit > "$demo_dir/both") 2>/dev/null; then
  printf 'unexpected overwrite\n'
else
  printf 'noclobber rejected overwrite\n'
fi
demo_ran=no
mark_run() { demo_ran=yes; }
if mark_run 2>/dev/null > "$demo_dir/missing/output"; then
  printf 'unexpected redirection\n'
else
  printf 'function-ran=%s\n' "$demo_ran"
fi
BASH

The combined file prints out and err. Reversed ordering leaves only out in the output file and captures err separately. The transformation reports an empty input because truncation preceded execution. The remaining lines confirm that noclobber rejected replacement and the failed open left function-ran=no.

These checks observe file contents and whether the command actually ran, rather than relying on a successful final print. In a real script, retain the producer’s exit status alongside its logs: a perfectly routed error message does not turn a failed operation into success. Keeping output routing and operation status separate makes recovery decisions reliable.

Further reading

Sources