Redirecting stderr to log file while preserving stdout to console in Linux/Unix shell


1 views

When working with shell scripts, we often need to handle stderr and stdout streams differently. The common requirement is to log errors while keeping both streams visible in the console. The naive approach of 2>&1 | tee combines both streams, which isn't what we want.

In Unix-like systems, file descriptors are crucial for stream handling:


0 - stdin
1 - stdout
2 - stderr

Here's how to redirect stderr to a log file while preserving normal console output:


command 2> >(tee -a error.log >&2)

Breaking this down:

  • 2> redirects stderr
  • >(command) is process substitution
  • tee -a appends to the log file
  • >&2 sends the output back to stderr

Let's demonstrate with a script that generates both output and errors:


#!/bin/bash

# Function that produces both stdout and stderr
test_streams() {
    echo "This goes to stdout"
    echo "This goes to stderr" >&2
}

# Redirect only stderr to log
test_streams 2> >(tee -a script_errors.log >&2)

For systems without process substitution support (like some old bash versions), use:


{ command 2>&1 >&3 | tee -a error.log >&2; } 3>&1

For production systems, consider adding log rotation:


LOG_FILE="app_errors_$(date +%Y-%m-%d).log"
command 2> >(tee -a "$LOG_FILE" >&2)

To completely separate streams into different files:


command >output.log 2> >(tee -a error.log >&2)

When working with command-line tools in Unix/Linux systems, we often need to separate stderr (standard error) from stdout (standard output) for proper logging. The common approach of 2>&1 | tee combines both streams, which isn't always desirable.

The typical solution:

command 2>&1 | tee logfile

merges both streams, making it impossible to:

  • See clean stdout in terminal
  • Get pure stderr in the log file
  • Maintain proper stream separation

Here are three effective methods to achieve stderr-only piping:

Method 1: Process Substitution

command 2> >(tee -a error.log >&2)

This approach:

  • Directs stderr to a process substitution
  • Preserves stdout to terminal
  • Writes stderr to both terminal and log file

Method 2: Using Temporary File Descriptors

{ command 2>&3 | tee /dev/fd/4; } 3>&1 4>&1 >/dev/tty

Key points:

  • Creates temporary file descriptors (3 and 4)
  • Routes stderr through descriptor 3
  • Maintains stdout on terminal

Method 3: Bash-specific Solution

exec 3>&1
command 2>&1 >&3 | tee error.log
exec 3>&-

This method:

  • Saves stdout to descriptor 3
  • Redirects stderr to pipe
  • Restores stdout to terminal

Here's how these techniques work in real scenarios:

Example: Logging Compilation Errors

gcc program.c 2> >(tee -a build_errors.log >&2)

Example: Server Startup Logging

./start_server.sh 2> >(tee -a server_errors.log >&2)
  • For scripts, use explicit file descriptor cleanup
  • Consider adding timestamps to error logs
  • Document the redirection approach in script headers
  • Test with both stdout and stderr generating commands

Be aware that:

  • Process substitution may not work in all shells
  • File descriptor leakage can occur if not properly closed
  • The order of mixed stdout/stderr output may vary