skip to content

In a bash script, why does `cmd 2>&1 > out.log` behave differently from `cmd > out.log 2>&1`, and where does stderr end up in each case?

level: middleimportance: must knowfreq 72%

answer

  1. applied left to right, not declaratively
  2. it copies, it does not follow
  3. where does fd 1 point yet?
  4. 2>&1 must come after >file

basics

~20 s

Bash applies redirections left to right, and each one copies where the target points at that moment. cmd > out.log 2>&1 puts stdout in the file and then makes stderr a copy of it, so both are captured. cmd 2>&1 > out.log copies stderr to the terminal first, then moves only stdout.

solid answer

~50 s

Redirections are not a set of declarations — the shell performs them one at a time, left to right, and `n>&m` means "make descriptor n a duplicate of wherever descriptor m points **right now**". In `cmd > out.log 2>&1`, fd 1 is pointed at the file first, then fd 2 is made a copy of fd 1, so both streams land in `out.log`. In `cmd 2>&1 > out.log`, fd 2 is first made a copy of fd 1 while fd 1 is still the terminal, and only afterwards is fd 1 moved to the file — so stderr keeps going to the terminal and the log holds stdout alone. It is the classic "my errors are missing from the log" bug; ShellCheck flags it as SC2069. The rule to remember: `2>&1` must come *after* the stdout redirection it is supposed to follow.

code

bash · 8 lines
bash
#!/usr/bin/env bash
both() { echo "to stdout"; echo "to stderr" >&2; }

both > a.log 2>&1
both 2>&1 > b.log

echo "--- a.log (both streams) ---"; cat a.log
echo "--- b.log (stdout only) ---"; cat b.log

go deeper

for a junior

Memorise the working order: put > file first and 2>&1 after it, or use &> file in a bash script. Be able to say that 1 is stdout and 2 is stderr.

for a middle

Explain the mechanism, not the recipe: redirections run left to right and n>&m duplicates where m points at that instant. Walk both orders aloud, naming what each descriptor points at after each step.

for a senior

Show where this bites in production — a cron or CI job whose archived log is missing exactly the errors someone needs. Mention ShellCheck SC2069 and redirecting a { ...; } group as the readable fix.

for a principal

Own the convention: decide whether the codebase logs both streams together or keeps stderr separate for alerting, and make it uniform. Note that &> buys safety at the cost of POSIX portability, and pick one rule for all scripts.

## What a redirection actually is Every process starts with three open file descriptors: 0 (stdin), 1 (stdout) and 2 (stderr). A file descriptor is just a small integer index into a per-process table; each entry points at an open file, pipe, terminal or socket. A shell redirection is the shell editing that table for the command it is about to run — conceptually a `dup2()` call — *before* the program starts. The program itself knows nothing about it: it writes to fd 1 and fd 2 as always, and the table decides where those bytes go. Two forms matter here: - `n> file` — open `file` and install it at descriptor `n`. - `n>&m` — make descriptor `n` a **duplicate** of descriptor `m`, meaning both entries now point at the same open file. The word *duplicate* is the whole lesson. `2>&1` does not say "stderr follows stdout from now on". It says "copy the current value of entry 1 into entry 2, once". ## Left to right, one at a time Bash processes the redirection list of a command strictly left to right. Walk both orders with a terminal as the starting point: ```bash # 1 -> tty, 2 -> tty make > build.log 2>&1 # > build.log : 1 -> build.log (2 still -> tty) # 2>&1 : 2 -> copy of 1 -> build.log # result: both streams in build.log # 1 -> tty, 2 -> tty make 2>&1 > build.log # 2>&1 : 2 -> copy of 1 -> tty (fd 1 has not moved yet!) # > build.log : 1 -> build.log (fd 2 is untouched) # result: stdout in build.log, stderr on the terminal ``` The second form is not a syntax error and prints no warning. In an interactive terminal it even looks half right, because the compiler errors you wanted are still visible — on screen. In cron or CI, where there is no terminal, those bytes go wherever the job's stderr goes, and the log file you archived is silently incomplete. ## Why the second order is not useless Swapping the order deliberately is the standard way to send the two streams to *different* places, because the copy is taken before the move: ```bash # stdout to a file, stderr through a pipe to a filter make 2>&1 >build.log | grep -i warning ``` Here the pipe is set up before any redirection, so at the moment `2>&1` runs, fd 1 is the pipe: stderr goes down the pipe to `grep`, and then fd 1 is moved to `build.log`. This idiom — "swap stderr onto the pipe, then park stdout in a file" — is the reason the order is not simply a mistake to be linted away. ## Ordering inside a pipeline A related trap is that the pipe is established first, for the whole pipeline, and only then are each stage's own redirections applied. So `cmd 2>&1 | less` really does send both streams to `less`, even though `2>&1` appears with nothing before it: fd 1 was already the pipe when the duplication happened. ## Grouping as the readable alternative When the redirection list gets confusing, redirect a group instead — the group's descriptors are set up once, and everything inside inherits them: ```bash { echo "starting" make } > build.log 2>&1 ``` This is also the fix ShellCheck suggests for SC2069 when you genuinely wanted both streams in the file. ## The shorthand, and its cost Bash has `&> file` as shorthand for `> file 2>&1`, which cannot be ordered wrongly. It is a bashism: POSIX `sh` does not define it, and in a `#!/bin/sh` script `cmd &> file` can parse as `cmd &` followed by `> file`, running the command in the background instead. If the script must be portable, write `> file 2>&1` and get the order right. ## What to say in the interview State the mechanism, not the mnemonic: redirections are applied in order and `n>&m` copies the *current* target of `m`. Everything else — why the log is missing errors, why the deliberate swap works, why `&>` cannot go wrong — falls out of that one sentence.

  • So how do you deliberately send stdout to a file and stderr into a pipe?
    Rely on the same ordering rule: `cmd 2>&1 >out.log | grep -i error`. The pipeline is built first, so at the moment `2>&1` runs fd 1 is the pipe — stderr is duplicated onto the pipe — and only then is stdout moved to the file. Reversing the two redirections would send both to the file and leave `grep` with nothing.
  • Why does `cmd 2>&1 | less` show stderr in the pager even though the `2>&1` comes first?
    Because the pipe is not a redirection in the list — bash creates the pipeline and attaches fd 1 of the left-hand command to it before applying that command's own redirections. So fd 1 already points at the pipe when `2>&1` duplicates it, and both streams flow to `less`.
  • Is `&> file` an acceptable substitute, and where would you avoid it?
    In bash it is exactly `> file 2>&1` and cannot be mis-ordered, so it is fine in a `#!/usr/bin/env bash` script. Avoid it in anything with a `#!/bin/sh` shebang: it is not POSIX, and a strict `sh` can read `cmd &> file` as backgrounding `cmd` and then redirecting, which changes the program's meaning silently.

saying these in an interview costs you the question

  • Claiming 2>&1 permanently ties stderr to stdout
  • Saying redirection order never matters, only the operators
  • Reading 2>&1 as redirecting stderr into a file named 1
  • Assuming a missing-errors log means the tool printed nothing
  • Thinking the pipe is applied after the stage's redirections

context