/dev/null and Discarding Output

/dev/null is a special file built into every Linux system that quietly throws away anything written to it. When a command produces output you don’t care about — a noisy status message, a warning you already know about, an entire log stream from a cron job — you redirect it to /dev/null instead of letting it clutter your terminal, fill a log file, or trigger an unwanted email from cron. Understanding exactly how it works, and how to target stdout and stderr independently, is essential for writing clean shell scripts.

Overview / How it works

/dev/null is a character device file, not a regular file. You can see this with ls -l /dev/null: it shows a c in the first column (character device) and a major/minor device number (1, 3 on Linux) instead of a file size. It exists on essentially every Unix-like system and is created automatically at boot (or by udev), so you never need to create it yourself.

Every process starts with three open file descriptors: 0 (stdin, input), 1 (stdout, normal output), and 2 (stderr, error output). The shell can rewire any of these to point at a different destination before running a command — this is what redirection operators like >, >>, and 2> do. When you redirect a descriptor to /dev/null, the kernel opens that device file and points the descriptor at it instead of the terminal.

The null device driver itself is trivially simple:

  • Writes to /dev/null are accepted and immediately discarded. The driver’s write handler reports success (it returns the exact number of bytes it was given) without storing those bytes anywhere — no disk I/O happens, no inode is touched, and the “file” never grows. This is why /dev/null always shows a size of 0 no matter how much you write to it.
  • Reads from /dev/null return end-of-file (EOF) immediately — zero bytes, every time. This makes it useful as a source of “no input” for commands that expect to read from stdin but shouldn’t block waiting for a human to type something.

Because writing to /dev/null is nearly free (no disk access, just a kernel call that returns instantly), it’s also a cheap way to discard output when you only care about a command’s exit status, not what it printed.

Syntax

The general forms for sending a stream to /dev/null (or reading no input from it) are:

command > /dev/null          # discard stdout only
command 2> /dev/null         # discard stderr only
command > /dev/null 2>&1     # discard stdout AND stderr (order matters)
command &> /dev/null          # Bash shorthand for the line above
command < /dev/null          # feed empty input (immediate EOF) to a command
Form Meaning
> /dev/null Redirect file descriptor 1 (stdout) to the null device; overwrites, doesn’t append.
2> /dev/null Redirect file descriptor 2 (stderr) to the null device, leaving stdout on the terminal.
2>&1 “Duplicate file descriptor 1’s current target onto file descriptor 2.” Must come after > /dev/null to have stderr follow stdout into the null device.
&> /dev/null Bash-only shorthand that redirects both stdout and stderr to /dev/null in one step.
< /dev/null Redirect stdin from the null device, so any read the command performs returns EOF instantly.

Examples

Example 1: Discard stdout, keep stderr visible

ls /var/log/app.log /var/log/missing.log > /dev/null

Output:

ls: cannot access '/var/log/missing.log': No such file or directory

ls writes the successful listing (/var/log/app.log) to stdout, which we sent to /dev/null, so it never appears. The “No such file or directory” message goes to stderr, which was left pointing at the terminal, so it still prints. This pattern — hide the normal output, keep the errors — is the most common reason to use /dev/null.

Example 2: Discard stderr, keep stdout visible

ls /var/log/app.log /var/log/missing.log 2> /dev/null

Output:

/var/log/app.log

Now it’s reversed: the error about the missing file is thrown away, and only the successful result prints. This is handy when you expect some failures (say, checking several optional config paths) and only want to see what actually exists.

Example 3: Silence a command completely and check only its exit code

if curl -sf https://example.com/health > /dev/null 2>&1; then
  echo "Service is up"
else
  echo "Service check failed"
fi

Output:

Service is up

Here we don’t care about the response body or any curl progress/error text — only whether the request succeeded. Both streams go to /dev/null, and the if statement inspects curl’s exit status ($?, checked implicitly by if) to decide which branch runs. This is the standard shape of a health check or availability test in a script.

How it works step by step

For a command like backup.sh > /dev/null 2>&1, Bash performs these steps before your program ever runs:

  1. The shell forks a child process to run backup.sh.
  2. In that child, before exec-ing the script, the shell processes redirections left to right. > /dev/null opens /dev/null for writing and uses dup2() to make file descriptor 1 (stdout) point at it.
  3. 2>&1 then duplicates whatever file descriptor 1 currently points to (the now-redirected /dev/null) onto file descriptor 2 (stderr).
  4. The child process calls exec() to become backup.sh, inheriting the two rewired descriptors.
  5. Anything the script writes to stdout or stderr now lands in the null device’s write handler and is discarded; the script itself has no idea its output is going nowhere.
  6. When the script exits, its exit status is still reported normally — redirection only affects the data streams, never the exit code.

Common Mistakes

Mistake 1: Getting the redirection order backwards

backup.sh 2>&1 > /dev/null

This looks like it should silence everything, but it doesn’t. Redirections are applied left to right: 2>&1 first points stderr at whatever stdout currently is — the terminal — and then > /dev/null repoints stdout to the null device. Stderr is left attached to the terminal, so error messages still print. The fix is to send stdout to /dev/null first, then duplicate stderr onto it:

backup.sh > /dev/null 2>&1

Mistake 2: Using /dev/null to hide errors instead of fixing them

rm -f /tmp/app.lock 2> /dev/null
cp config.yaml /etc/myapp/config.yaml 2> /dev/null

Blanket-suppressing stderr on every command “just in case” is a common way to hide a real, silent failure (a typo’d path, a permissions problem) that then causes confusing behavior later. Only discard stderr when you’ve deliberately decided a specific failure is safe to ignore, and prefer checking the exit code explicitly so you still know something went wrong:

if ! cp config.yaml /etc/myapp/config.yaml; then
  echo "Failed to install config" >&2
  exit 1
fi

Mistake 3: Confusing > /dev/null with >> /dev/null

Some people reach for >> /dev/null out of habit, thinking append is “safer.” It makes no practical difference for /dev/null itself (nothing is ever stored either way), but it’s worth remembering that > truncates and >> appends for every other file — using > by habit on a real log file you meant to append to will silently wipe it.

Best Practices

  • Redirect stdout and stderr separately (> /dev/null, then 2> /dev/null) when you want to keep visibility into errors while silencing normal output — don’t reach for &> /dev/null by default.
  • When you do need to discard both streams, write > /dev/null 2>&1 (or the Bash shorthand &> /dev/null) and remember the ordering rule for the long form.
  • In cron jobs, redirect a command’s own output to a real log file instead of /dev/null when you might need to debug it later; only use /dev/null for output you’ve confirmed is genuinely noise.
  • Use < /dev/null for commands launched in the background or over ssh that might otherwise try to read from a terminal and hang.
  • Still check $? (or use the command directly in an if) after discarding output — silencing a stream never silences failure; you can and should keep checking whether the command succeeded.
  • Don’t try to “empty” /dev/null or change its permissions; it’s a shared system device maintained by the kernel/udev, not a regular file you manage.

Practice Exercises

  • Write a command that lists both /etc/passwd and a file that doesn’t exist, showing only the error message (hide the successful listing).
  • Write an if statement that runs ping -c 1 8.8.8.8, discards all of its output, and prints either "Network reachable" or "Network unreachable" based on the exit code.
  • Take the (broken) line myscript.sh 2>&1 > /dev/null and rewrite it so that both stdout and stderr are actually discarded. Explain in one sentence why the original order failed.

Summary

  • /dev/null is a character device that discards everything written to it and returns EOF immediately when read — no disk I/O, no size, nothing recoverable.
  • > /dev/null discards stdout only; 2> /dev/null discards stderr only; > /dev/null 2>&1 (or &> /dev/null) discards both.
  • Redirection order matters: 2>&1 must come after stdout has already been pointed at /dev/null, not before.
  • < /dev/null supplies immediate EOF as input, useful for background or non-interactive commands.
  • Discarding output never discards the exit status — keep checking $? or using the command in a conditional so failures aren’t silently lost along with the text.
  • Use /dev/null deliberately for known noise, not as a blanket way to hide errors you haven’t investigated.