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?
answer
- three descriptors, not one
- the bare operator has a default
- numbers glue to the operator
- errors ride a different channel
- 2> for descriptor two
basics
~20 sA 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 sEvery 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
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.
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.
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.
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