9.1. Standard Streams and File Descriptors#

9.1.1. Common Pitfalls#

1. Confusing descriptor numbers (0=stdin, 1=stdout, 2=stderr)

# Wrong: trying to redirect stdin
command > 0 /dev/null  # Invalid

# Right: redirect stdout to null
command > /dev/null

# Right: redirect stderr to null
command 2> /dev/null

2. Forgetting that > overwrites files

# Dangerous: this overwrites the log!
echo "Line 1" > log.txt
echo "Line 2" > log.txt  # Line 1 is gone!

# Better: use append
echo "Line 1" > log.txt
echo "Line 2" >> log.txt

3. Losing stderr by redirecting only stdout

# command_with_error runs, error goes to console
command_with_error > /tmp/output.txt

# Better: also redirect stderr
command_with_error > /tmp/output.txt 2> /tmp/errors.txt

# Or combine them
command_with_error > /tmp/output.txt 2>&1

4. Order matters in redirections

# Wrong: 2>&1 must come after >
echo "test" 2>&1 > file.txt  # stderr goes to console!

# Right: specify in correct order
echo "test" > file.txt 2>&1  # Both go to file

9.1.2. Real-World Example: Logging with Multiple Streams#

#!/bin/bash

LOG_FILE="/var/log/app.log"
ERROR_LOG="/var/log/app.err"

# Function to log messages
log_message() {
  local msg="$1"
  local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
  
  # Log to file and console
  echo "[$timestamp] $msg" | tee -a "$LOG_FILE"
}

# Function to log errors
log_error() {
  local msg="$1"
  local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
  
  # Log to stderr and error file
  echo "[$timestamp] ERROR: $msg" | tee -a "$ERROR_LOG" >&2
}

# Example usage
log_message "Application started"
log_error "Failed to connect to database"

# Process file while logging progress
while IFS= read -r line; do
  log_message "Processing: $line"
done < input_file.txt

This pattern separates stdout logging (via tee) from error reporting.

9.1.3. Here Documents and Here Strings#

9.1.3.1. Here Documents (<<)#

A here document sends multiple lines to a command’s stdin:

#!/bin/bash

# Basic here document
cat << EOF
This is a here document.
It can span multiple lines.
Variables are expanded: $USER
EOF

# Prevent variable expansion (quote the delimiter)
cat << 'EOF'
Variables are NOT expanded: $USER
EOF

# Use - to indent the closing delimiter
cat <<- EOF
    This content is indented
    The - allows indentation of EOF
EOF

9.1.3.2. Here Strings (<<<)#

Send a single string to stdin:

#!/bin/bash

# Here string
read var <<< "Hello from here string"
echo $var  # Output: Hello from here string

# Process a string with a filter
grep "bash" <<< "This is a bash script"

# Calculate with bc
result=$(bc <<< "2 + 2")
echo $result  # Output: 4

9.1.4. File Descriptors and Advanced Redirection#

File descriptors are numeric references to open files/streams. You can work with any descriptor 0-9 (or higher in some shells).

9.1.4.1. Using Custom File Descriptors#

#!/bin/bash

# Open a file for reading on descriptor 3
exec 3< /etc/passwd

# Read from descriptor 3
read -u 3 first_line
echo "First line of /etc/passwd: $first_line"

# Close descriptor 3
exec 3<-

# Open a file for writing on descriptor 4
exec 4> /tmp/output.txt
echo "Line written to descriptor 4" >&4
exec 4>-

9.1.4.2. Duplicating File Descriptors#

#!/bin/bash

# Save stdout to descriptor 3
exec 3>&1

# Redirect stdout to a file
exec 1> /tmp/output.txt

echo "This goes to /tmp/output.txt"

# Restore stdout from descriptor 3
exec 1>&3

echo "This goes to the terminal"

# Close descriptor 3
exec 3>&-

9.1.5. Redirecting Output#

9.1.5.1. Redirect to File#

# Overwrite file (>)
echo "First line" > /tmp/log.txt
echo "Second line" > /tmp/log.txt  # This overwrites!

# Append to file (>>)
echo "First line" > /tmp/log.txt
echo "Second line" >> /tmp/log.txt  # This appends

# Result:
# First line
# Second line

9.1.5.2. Redirect stderr Separately#

# Send success to file, errors to console
ls /tmp /nonexistent > /tmp/success.txt

# Send errors to file, success to console
ls /tmp /nonexistent 2> /tmp/errors.txt

# Send both to different files
ls /tmp /nonexistent > /tmp/success.txt 2> /tmp/errors.txt

# Send both to same file
ls /tmp /nonexistent > /tmp/all.txt 2>&1
# Or in newer bash:
ls /tmp /nonexistent &> /tmp/all.txt

9.1.5.3. Suppress Output#

# Discard stdout
ls /tmp > /dev/null

# Discard stderr
ls /nonexistent 2> /dev/null

# Discard both
ls /tmp /nonexistent > /dev/null 2>&1

9.1.6. The Three Standard Streams#

Every Unix process has three standard I/O streams:

Stream

Descriptor

Default

Purpose

stdin

0

keyboard

Input to the program

stdout

1

terminal

Normal output from the program

stderr

2

terminal

Error messages from the program

9.1.6.1. Using stdin (file descriptor 0)#

# Read from stdin (waits for user input)
read -p "Enter your name: " name
echo "Hello, $name"

# Read from a file instead of keyboard
read variable < /path/to/file

# Read from a pipe
echo "some data" | read data

9.1.6.2. Using stdout (file descriptor 1)#

# Normal output (default)
echo "This goes to stdout"
ls /tmp

# Explicit redirect to stdout
echo "Message" 1> /tmp/output.txt

# Append to file
echo "Line 2" 1>> /tmp/output.txt

9.1.6.3. Using stderr (file descriptor 2)#

# Error output goes to stderr
ls /nonexistent 2> /tmp/errors.txt

# Both stdout and stderr to same file
ls /tmp /nonexistent &> /tmp/all.txt

# Or use 2>&1 syntax
ls /tmp /nonexistent > /tmp/all.txt 2>&1