Bash Output Control: Redirect stdout, stderr, and Pipelines

Reliable scripts are not only about what commands run. They are also about where output goes, what gets logged, and how downstream commands receive input.

Table of Contents

This post focuses on output routing patterns you will use in almost every script.

Output Streams

Combining and Controlling Streams

Pipelines and Logging

Series Navigation

Output streams

stdout and stderr in practice

Most commands write normal output to stdout (file descriptor 1) and errors to stderr (file descriptor 2).

echo "normal message"
ls /does-not-exist

Overwrite versus append redirects

Use > to overwrite and >> to append.

echo "first line" > output.log
echo "second line" >> output.log

Combining and controlling streams

Redirect stderr only

You can silence only errors while leaving normal output visible.

ls /etc /does-not-exist 2>/dev/null

Merge stdout and stderr

Route both streams to one place when troubleshooting or collecting logs.

./run-job.sh > job.log 2>&1
./run-job.sh &> job.log
./run-job.sh &>> job.log

2>&1 means “send stderr to wherever stdout currently goes”.

Avoid accidental overwrite

If you want basic protection against clobbering files, enable noclobber.

set -o noclobber
echo "safe write" > existing.log

When you intentionally need overwrite semantics, disable it for that script section.

Pipelines and logging

Pipe command output

Pipes send stdout from one command into stdin of the next command.

ps aux | grep nginx | wc -l

Use tee for live output plus log files

tee lets you keep output in the terminal while writing the same data to a file.

./deploy.sh 2>&1 | tee deploy.log

Append mode preserves previous logs.

./deploy.sh 2>&1 | tee -a deploy.log

Capture success and failure cleanly

A small pattern that keeps output organized:

#!/usr/bin/env bash

run_report() {
  local report_file="$1"

  if ./build-report.sh >"$report_file" 2>error.log; then
    echo "report generation succeeded"
    return 0
  fi

  echo "report generation failed, see error.log"
  return 1
}

Next in this series

Next, we will use stdin and read, then move into positional arguments so scripts can accept interactive input and command-line parameters.