skip to content

Standard Streams and Basic Redirection

Every process starts with three open streams, and redirection simply rewires them before the command runs. Interviewers ask what `>file 2>&1` does compared with `2>&1 >file` because the order in which those duplications happen is the entire answer.

part ofBashoverview, primer and where to startread it →
on this pageshow

questions

5

In bash, running `mytool > out.log` still prints error messages to the terminal instead of putting them in out.log. Which stream does a bare `>` redirect, and how do you redirect the other one?

level: juniorimportance: must knowfreq 85%

answer

  1. three descriptors, not one
  2. the bare operator has a default
  3. numbers glue to the operator
  4. errors ride a different channel
  5. 2> for descriptor two

basics

~20 s

A bare > redirects only standard output, file descriptor 1. Error messages travel on standard error, descriptor 2, which stays on the terminal until you redirect it explicitly with 2> file, 2>> file, or 2>/dev/null.

solid answer

~40 s

Every process starts with three inherited descriptors: 0 standard input, 1 standard output, 2 standard error. A redirection operator with no number in front defaults to descriptor 1, so `mytool > out.log` is just shorthand for `mytool 1> out.log` and captures only normal output. Diagnostics written to descriptor 2 keep going wherever the shell's stderr points — usually your terminal. To capture them, put the number immediately before the operator: `mytool 2> err.log` to truncate-and-write, `2>> err.log` to append, `2>/dev/null` to throw them away. If you want both streams in the same file, `mytool > both.log 2>&1` (or bash's `&> both.log`) does that. Remember `>` truncates the target to zero bytes while `>>` appends, and both create the file if it is missing.

go deeper

for a junior

Know the three descriptors by number and say plainly that > redirects only stdout, so stderr needs 2>. Being able to write 2>/dev/null and 2>> errors.log from memory is the whole bar here.

for a middle

Explain that the shell — not the command — sets up redirections and strips them from the argument list, and that > truncates while >> opens in append mode. Mention < /dev/null for non-interactive runs.

for a senior

Show the operational instinct: when a log looks empty or a grep misses an error, check which descriptor the text was on. Argue for separate files when output is machine-parsed and a merged file when a human reads it.

for a principal

Own the convention as an interface contract across a fleet of scripts: stdout is the machine-readable product, stderr the human channel, so callers can silence or capture either without patching the script. Push for it in review the same way you would an API response shape.

## Three descriptors, already open before your code runs When the kernel starts a process it hands it a table of open file descriptors — small integers that index the process's open files. By convention the first three are already open and inherited from the parent: **0 = standard input**, **1 = standard output**, **2 = standard error**. A bash script does not open them; it inherits them from the shell, which inherited them from a terminal emulator, or from cron, systemd, or a CI runner. When you run something from an interactive terminal all three point at the same terminal device, which is exactly why the distinction stays invisible until you redirect one of them. The division of labour is a convention, not a kernel rule: descriptor 1 carries the command's *product* (the data another program would consume), descriptor 2 carries *messages about* the run — warnings, progress, errors. `ls`, `grep`, `curl` and friends all honour it. ## `>` is shorthand for `1>` The redirection grammar is `[n]>word`, where `n` is an optional descriptor number that must be written with **no space** before the operator. Omitting `n` means 1. So these are identical: ```bash mytool > out.log mytool 1> out.log ``` A classic slip is writing a space: `mytool 2 > out.log` passes the string `2` to `mytool` as an argument and redirects stdout — because the shell only treats `2` as a descriptor number when it is glued to the `>`. Redirections are recognised by the shell, applied to the new process, and **removed from the command line** before the command runs. `mytool` never sees `>` or `out.log` in its argument list; it simply finds descriptor 1 already attached to a file. ## Redirecting standard error ```bash ls /etc /nope > out.log # listing to the file, error to the terminal ls /etc /nope > out.log 2> err.log # each stream to its own file ls /etc /nope 2>/dev/null # errors discarded, listing on screen ls /etc /nope 2>> err.log # errors appended to an existing log ``` Keeping them in separate files is usually the right choice for anything a machine will parse: a downstream `wc -l` or `jq` on `out.log` is not polluted by a warning line. Merging both into one file (`> both.log 2>&1`, or bash's non-POSIX `&> both.log`) is for human-read logs. ## Truncate, append, and reading `>` opens the target with `O_WRONLY|O_CREAT|O_TRUNC` — the file is emptied the moment the shell sets up the redirection, before the command starts. `>>` opens with `O_APPEND`, so every write is positioned at the current end of file; that is why two processes appending to the same log interleave whole writes rather than overwriting each other. `< file` opens for reading and fails if the file does not exist, in which case the command is not run at all. Bash also has a guardrail: `set -o noclobber` (equivalently `set -C`) makes `>` refuse to overwrite an existing regular file, and `>|` forces the overwrite anyway. It is a per-shell option, so it protects your session, not other people's scripts. ## /dev/null and the special paths bash recognises `/dev/null` is a real character device that discards everything written to it and returns end-of-file immediately when read. Both directions are useful: ```bash noisy_tool > /dev/null # keep errors, drop normal output noisy_tool 2> /dev/null # keep output, drop diagnostics batch_job < /dev/null # guarantee it never blocks waiting on stdin ``` That last form matters for anything started by cron or `ssh host cmd`: a command that decides to read stdin gets an immediate EOF instead of hanging forever. Inside a redirection bash also special-cases the names `/dev/stdin`, `/dev/stdout`, `/dev/stderr` and `/dev/fd/N`, handling them itself even on systems whose `/dev` lacks those entries, plus `/dev/tcp/host/port` and `/dev/udp/host/port` for network connections. ## Why this is the first thing to check When someone says "my log file is empty but I saw output", or "my CI job printed the error but the artifact has nothing", the answer is almost always that the interesting text was on descriptor 2 and only descriptor 1 was redirected. Pipes have the same blind spot: `mytool | grep something` filters stdout only, and a stack trace on stderr sails straight past the grep to your screen.

  • Why does `mytool | grep ERROR` still show error text on your terminal even though grep filters it out?
    A pipe connects only descriptor 1 of the left-hand command to descriptor 0 of the right-hand one. Anything the tool writes to descriptor 2 bypasses the pipe entirely and goes wherever the shell's stderr points, usually the terminal, so grep never sees it and never gets the chance to filter it.
  • What is the difference between `cmd > file` and `cmd >> file` if the file does not exist yet?
    Nothing visible: both create it with the same default permissions and write from the beginning. The difference appears on an existing file — `>` truncates it to zero bytes first, while `>>` opens in append mode so each write lands at the current end, which is what makes concurrent appends to a shared log safe from mutual overwriting.
  • Why would you redirect stdin from /dev/null in a script run by cron?
    So no command in the script can block waiting for input that will never arrive. A cron job has no terminal, and a tool that decides to prompt or read stdin would otherwise hang until the job is killed. Reading /dev/null returns immediate end-of-file, so the tool takes its non-interactive path.

saying these in an interview costs you the question

  • Thinking > captures everything a command prints
  • Writing a space, as in cmd 2 > file
  • Believing errors are lost rather than unredirected
  • Assuming a pipe carries stderr too
  • Confusing 2>file with 2>>file when logging

context

open as a page

A colleague runs `sort data.txt > data.txt` in bash and data.txt comes back empty. Why does the file lose its contents, and what is the correct way to filter a file in place?

level: middleimportance: must knowfreq 62%

basics

~20 s

The shell performs redirections before it runs sort, so > data.txt truncates the file to zero bytes first and sort then reads an empty file. Filter into a temporary file and rename it over the original instead.

open as a page

In bash, `sudo echo hello > /etc/motd` fails with "Permission denied" even though the user has full sudo rights. Which process is doing the redirection, and how do you write that file as root?

level: middleimportance: should knowfreq 52%

basics

~10 s

Your unprivileged shell performs the redirection, opening /etc/motd as you before sudo ever runs, so the open fails. Pipe into a privileged writer instead: echo hello | sudo tee /etc/motd.

open as a page

In a bash script whose output other programs consume, why should progress and warning messages go to standard error while only results go to standard output?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Standard output is the script's data channel: whatever lands there is what a pipeline, a redirect or a command substitution captures. Putting progress messages there corrupts the caller's data, while standard error keeps them visible and independently silenceable.

open as a page

A bash script asks a question with `read`, but when it is run as `./setup.sh > install.log` the prompt text disappears into the log and the run looks hung. Which stream should a prompt use, and what does /dev/tty give you here?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Prompts belong on standard error, not standard output, so they stay visible when a caller redirects the script's output to a log. Bash's read -p already prompts on stderr. /dev/tty writes straight to the controlling terminal, but fails when there is none.

open as a page