📥 Bash Input/Output & Redirection

Comprehensive Reference Guide — File descriptors, pipes, here-docs, tee, and advanced I/O patterns

📋 Table of Contents

Overview

Bash I/O redirection allows you to control where command input comes from and where output goes. Every process has three standard file descriptors: stdin (0), stdout (1), and stderr (2). Mastering redirection is essential for building pipelines, logging, and creating robust scripts.

Standard File Descriptors

stdin (0): Standard input - where commands read input from

stdout (1): Standard output - where commands write normal output

stderr (2): Standard error - where commands write error messages

Redirection Operators

Output Redirection

OperatorDescriptionExample
> Redirect stdout (overwrite) echo "text" > file.txt
>> Redirect stdout (append) echo "text" >> file.txt
2> Redirect stderr (overwrite) cmd 2> errors.log
2>> Redirect stderr (append) cmd 2>> errors.log
&> Redirect both stdout and stderr cmd &> all.log
>&2 Redirect stdout to stderr echo "error" >&2
2>&1 Redirect stderr to stdout cmd 2>&1

Input Redirection

OperatorDescriptionExample
< Redirect stdin from file sort < unsorted.txt
<<EOF Here-document cat <<EOF ... EOF
<<< Here-string grep x <<< "text"
<(cmd) Process substitution (input) diff <(cmd1) <(cmd2)
>(cmd) Process substitution (output) tee >(cmd)

Pipes

OperatorDescriptionExample
| Pipe stdout to stdin ls | grep txt
|& Pipe both stdout and stderr cmd |& grep error
⚠️ Order Matters
cmd > file 2>&1 redirects both to file, but cmd 2>&1 > file only redirects stdout to file (stderr goes to original stdout).

Detailed Examples

1
Basic Output Redirection

Redirecting stdout and stderr to files.

#!/bin/bash
# Basic output redirection

echo "=== Overwrite vs Append ==="

# Create/overwrite file
echo "First line" > /tmp/output.txt
echo "Second line" > /tmp/output.txt  # Overwrites!
echo "Content of file:"
cat /tmp/output.txt

# Append to file
echo "Line 1" > /tmp/append.txt
echo "Line 2" >> /tmp/append.txt
echo "Line 3" >> /tmp/append.txt
echo ""
echo "Appended content:"
cat /tmp/append.txt

echo ""
echo "=== Redirect stderr ==="

# stdout goes to terminal, stderr to file
ls /nonexistent 2> /tmp/errors.log
echo "Errors saved to file:"
cat /tmp/errors.log

# Append errors
ls /also_nonexistent 2>> /tmp/errors.log
echo ""
echo "All errors:"
cat /tmp/errors.log

echo ""
echo "=== Redirect Both stdout and stderr ==="

# Method 1: &> (Bash 4+)
ls /tmp /nonexistent &> /tmp/all.log
echo "Combined output (&>):"
cat /tmp/all.log

# Method 2: 2>&1 (traditional)
ls /tmp /nonexistent > /tmp/all2.log 2>&1
echo ""
echo "Combined output (2>&1):"
cat /tmp/all2.log

echo ""
echo "=== Separate stdout and stderr ==="

# Different files for each
ls /tmp /nonexistent > /tmp/stdout.log 2> /tmp/stderr.log

echo "stdout:"
cat /tmp/stdout.log
echo ""
echo "stderr:"
cat /tmp/stderr.log

echo ""
echo "=== Discard Output ==="

# Send to /dev/null
echo "This is discarded" > /dev/null

# Discard errors only
ls /nonexistent 2> /dev/null
echo "No error shown"

# Discard everything
ls /tmp /nonexistent &> /dev/null
echo "Nothing shown"

echo ""
echo "=== Write to stderr ==="

# Send message to stderr
echo "This is an error message" >&2

# Practical use in scripts
log_error() {
    echo "[ERROR] $1" >&2
}

log_info() {
    echo "[INFO] $1"
}

log_info "Starting process"
log_error "Something went wrong"

# Cleanup
rm -f /tmp/output.txt /tmp/append.txt /tmp/errors.log 
rm -f /tmp/all.log /tmp/all2.log /tmp/stdout.log /tmp/stderr.log
Output: === Overwrite vs Append === Content of file: Second line Appended content: Line 1 Line 2 Line 3 === Redirect stderr === Errors saved to file: ls: cannot access '/nonexistent': No such file or directory === Redirect Both stdout and stderr === Combined output (&>): /tmp: file1.txt file2.txt ls: cannot access '/nonexistent': No such file or directory === Write to stderr === [INFO] Starting process [ERROR] Something went wrong
2
Input Redirection

Reading input from files and strings.

#!/bin/bash
# Input redirection

echo "=== Basic Input Redirection ==="

# Create test file
cat > /tmp/data.txt << 'EOF'
banana
apple
cherry
date
EOF

# Read from file
echo "Sorted from file:"
sort < /tmp/data.txt

echo ""
echo "=== Here-String (<<<) ==="

# Single line input
grep "o" <<< "hello world"

# Variable as input
text="The quick brown fox"
wc -w <<< "$text"

# Process string
upper=$(tr 'a-z' 'A-Z' <<< "hello")
echo "Uppercase: $upper"

echo ""
echo "=== Here-Document (<<) ==="

# Multi-line input
cat << 'EOF'
This is line 1
This is line 2
This is line 3
EOF

echo ""

# With variable expansion
name="World"
cat << EOF
Hello, $name!
Today is $(date +%A)
EOF

echo ""
echo "=== Prevent Variable Expansion ==="

# Quote the delimiter to prevent expansion
cat << 'NOEXPAND'
Variable: $HOME
Command: $(whoami)
NOEXPAND

echo ""
echo "=== Indented Here-Doc (<<-) ==="

# <<- strips leading tabs (not spaces)
	cat <<- EOF
	This line has leading tabs
	They are stripped
	EOF

echo ""
echo "=== Read Lines from File ==="

while IFS= read -r line; do
    echo "Line: $line"
done < /tmp/data.txt

echo ""
echo "=== Combine Input and Output ==="

# Read from file, write to file
sort < /tmp/data.txt > /tmp/sorted.txt
echo "Sorted output:"
cat /tmp/sorted.txt

echo ""
echo "=== Multiple Inputs ==="

# Using process substitution for multiple
paste <(seq 1 3) <(seq 4 6)

# Cleanup
rm -f /tmp/data.txt /tmp/sorted.txt
Output: === Basic Input Redirection === Sorted from file: apple banana cherry date This is line 2 This is line 3 Hello, World! Today is Thursday === Prevent Variable Expansion === Variable: $HOME Command: $(whoami) === Read Lines from File === Line: banana Line: apple Line: cherry Line: date === Multiple Inputs === 1 4 2 5 3 6
3
Pipes and Pipelines

Connecting commands with pipes for data processing.

#!/bin/bash
# Pipes and pipelines

echo "=== Basic Pipe ==="

# Connect stdout to stdin
echo -e "cherry\napple\nbanana" | sort

echo ""
echo "=== Multi-stage Pipeline ==="

# Chain multiple commands
cat /etc/passwd | grep -v "^#" | cut -d: -f1 | sort | head -5

echo ""
echo "=== Pipeline with Processing ==="

# Generate, filter, transform, count
seq 1 100 | grep '7' | wc -l
echo "Numbers 1-100 containing '7'"

echo ""
echo "=== Pipe stderr Too (|&) ==="

# Pipe both stdout and stderr
ls /tmp /nonexistent |& grep -i "no such"

echo ""
echo "=== Named Pipe (FIFO) ==="

# Create named pipe
mkfifo /tmp/mypipe

# Writer (background)
echo "Message through pipe" > /tmp/mypipe &

# Reader
cat < /tmp/mypipe

rm /tmp/mypipe

echo ""
echo "=== Practical: Log Processing ==="

# Simulate log processing
cat << 'EOF' | grep ERROR | cut -d']' -f2 | sort | uniq -c
[INFO] Starting
[ERROR] Connection failed
[INFO] Retrying
[ERROR] Timeout
[ERROR] Connection failed
[INFO] Success
EOF

echo ""
echo "=== Practical: Data Transformation ==="

# CSV processing
echo "name,age,city
Alice,30,NYC
Bob,25,LA
Charlie,35,Chicago" | \
    tail -n +2 | \
    awk -F, '{print $1 " is " $2 " years old"}'

echo ""
echo "=== xargs with Pipe ==="

# Convert lines to arguments
echo -e "file1\nfile2\nfile3" | xargs -I {} echo "Processing {}"

echo ""
echo "=== Pipeline Exit Status ==="

# Check PIPESTATUS
false | true | false
echo "Exit statuses: ${PIPESTATUS[*]}"

# pipefail option
set -o pipefail
if false | true; then
    echo "Pipeline succeeded"
else
    echo "Pipeline failed (pipefail enabled)"
fi
set +o pipefail

echo ""
echo "=== tee in Pipeline ==="

# Split output to file and next command
echo "Hello World" | tee /tmp/saved.txt | tr 'a-z' 'A-Z'
echo "Saved: $(cat /tmp/saved.txt)"
rm /tmp/saved.txt
Output: === Basic Pipe === apple banana cherry === Multi-stage Pipeline === _apt backup bin daemon games === Pipeline with Processing === 19 Numbers 1-100 containing '7' === Pipe stderr Too (|&) === ls: cannot access '/nonexistent': No such file or directory === Named Pipe (FIFO) === Message through pipe === Practical: Log Processing === 2 Connection failed 1 Timeout === Practical: Data Transformation === Alice is 30 years old Bob is 25 years old Charlie is 35 years old === Pipeline Exit Status === Exit statuses: 1 0 1 Pipeline failed (pipefail enabled) === tee in Pipeline === HELLO WORLD Saved: Hello World
4
File Descriptors

Working with custom file descriptors beyond stdin/stdout/stderr.

#!/bin/bash
# File descriptors

echo "=== Standard Descriptors ==="
echo "stdin:  fd 0"
echo "stdout: fd 1"
echo "stderr: fd 2"

echo ""
echo "=== Open Custom File Descriptor ==="

# Open fd 3 for writing
exec 3> /tmp/fd3_output.txt

echo "This goes to stdout"
echo "This goes to fd 3" >&3
echo "Back to stdout"

# Close fd 3
exec 3>&-

echo "Content of fd 3 file:"
cat /tmp/fd3_output.txt
rm /tmp/fd3_output.txt

echo ""
echo "=== Open for Reading ==="

# Create test file
echo -e "Line 1\nLine 2\nLine 3" > /tmp/input.txt

# Open fd 4 for reading
exec 4< /tmp/input.txt

# Read from fd 4
read -r line1 <&4
read -r line2 <&4
echo "Read: $line1, $line2"

# Close fd 4
exec 4<&-
rm /tmp/input.txt

echo ""
echo "=== Read and Write Same File ==="

# Open fd 5 for both read and write
echo "Original content" > /tmp/rw.txt
exec 5<> /tmp/rw.txt

read -r content <&5
echo "Read: $content"

echo "New content" >&5

exec 5>&-
echo "File now contains:"
cat /tmp/rw.txt
rm /tmp/rw.txt

echo ""
echo "=== Duplicate File Descriptor ==="

# Save stdout
exec 3>&1

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

echo "This goes to file"

# Restore stdout
exec 1>&3
exec 3>&-

echo "This goes to terminal"
echo "File contains: $(cat /tmp/redirected.txt)"
rm /tmp/redirected.txt

echo ""
echo "=== Swap stdout and stderr ==="

# Useful for capturing only stderr
# 3=stdout, 1=stderr, 2=stdout
swap_demo() {
    echo "This is stdout"
    echo "This is stderr" >&2
}

echo "Normal:"
swap_demo

echo ""
echo "Swapped (capture only stderr):"
swap_demo 3>&1 1>&2 2>&3 | cat -n

echo ""
echo "=== Close stdin ==="

# Redirect stdin from /dev/null
# Useful for background daemons
cat < /dev/null
echo "stdin closed (empty input)"

echo ""
echo "=== Practical: Logging with fd ==="

# Setup log file on fd 3
exec 3>> /tmp/script.log

log() {
    echo "[$(date '+%H:%M:%S')] $1" >&3
}

log "Script started"
echo "Doing work..."
log "Work complete"

exec 3>&-

echo "Log file:"
cat /tmp/script.log
rm /tmp/script.log
Output: === Standard Descriptors === stdin: fd 0 stdout: fd 1 stderr: fd 2 === Open Custom File Descriptor === This goes to stdout Back to stdout Content of fd 3 file: This goes to fd 3 === Open for Reading === Read: Line 1, Line 2 === Duplicate File Descriptor === This goes to terminal File contains: This goes to file === Swap stdout and stderr === Normal: This is stdout This is stderr Swapped (capture only stderr): 1 This is stderr === Practical: Logging with fd === Doing work... Log file: [10:30:45] Script started [10:30:45] Work complete
5
Process Substitution

Using <() and >() for advanced I/O patterns.

#!/bin/bash
# Process substitution

echo "=== Input Process Substitution <() ==="

# Compare output of two commands
echo "Diff between two sorts:"
diff <(echo -e "a\nb\nc") <(echo -e "a\nB\nc")

echo ""
echo "=== Multiple Process Inputs ==="

# Paste columns from different sources
paste <(seq 1 3) <(seq 4 6) <(seq 7 9)

echo ""
echo "=== Avoid Subshell Variable Loss ==="

# Problem: variables lost in pipe subshell
count=0
echo -e "a\nb\nc" | while read line; do
    ((count++))
done
echo "With pipe: count=$count"  # 0!

# Solution: process substitution
count=0
while read line; do
    ((count++))
done < <(echo -e "a\nb\nc")
echo "With <(): count=$count"  # 3

echo ""
echo "=== Output Process Substitution >() ==="

# Send to multiple destinations
echo "Log message" | tee >(cat -n) >(wc -c > /tmp/count.txt)
sleep 0.1
echo "Character count: $(cat /tmp/count.txt)"
rm /tmp/count.txt

echo ""
echo "=== Practical: Compare Directories ==="

# Compare file listings
mkdir -p /tmp/dir1 /tmp/dir2
touch /tmp/dir1/{a,b,c}.txt
touch /tmp/dir2/{a,b,d}.txt

echo "Files only in dir1:"
comm -23 <(ls /tmp/dir1 | sort) <(ls /tmp/dir2 | sort)

echo "Files only in dir2:"
comm -13 <(ls /tmp/dir1 | sort) <(ls /tmp/dir2 | sort)

rm -rf /tmp/dir1 /tmp/dir2

echo ""
echo "=== Practical: Process Multiple Streams ==="

# Generate data and process in parallel
{
    echo "Data from source 1"
} | tee >(grep "1" > /tmp/has1.txt) \
      >(grep "source" > /tmp/hassource.txt) \
      > /dev/null

sleep 0.1
echo "Contains '1': $(cat /tmp/has1.txt)"
echo "Contains 'source': $(cat /tmp/hassource.txt)"
rm -f /tmp/has1.txt /tmp/hassource.txt

echo ""
echo "=== Feed Here-doc Through Process ==="

# Combine here-doc with process substitution
while read -r key value; do
    echo "$key = $value"
done < <(cat << 'EOF'
name John
age 30
city NYC
EOF
)

echo ""
echo "=== What Process Substitution Creates ==="

# It creates a file descriptor
echo "Process substitution path:"
echo <(echo "test")

# It's a file you can read
cat <(echo "Content from process substitution")
Output: === Input Process Substitution <() === Diff between two sorts: 2c2 < b --- > B === Multiple Process Inputs === 1 4 7 2 5 8 3 6 9 === Avoid Subshell Variable Loss === With pipe: count=0 With <(): count=3 === Output Process Substitution >() === 1 Log message Character count: 12 === Practical: Compare Directories === Files only in dir1: c.txt Files only in dir2: d.txt === What Process Substitution Creates === Process substitution path: /dev/fd/63 Content from process substitution
💡 Process Substitution vs Pipe
Use < <(cmd) instead of cmd | when you need to preserve variable changes in the current shell. Pipes create subshells.
6
tee for Multiple Outputs

Splitting output to multiple destinations.

#!/bin/bash
# tee for multiple outputs

echo "=== Basic tee ==="

# Output to both terminal and file
echo "Hello World" | tee /tmp/tee_output.txt
echo "File contains: $(cat /tmp/tee_output.txt)"
rm /tmp/tee_output.txt

echo ""
echo "=== Append with tee ==="

echo "Line 1" | tee /tmp/append.txt
echo "Line 2" | tee -a /tmp/append.txt
echo "Line 3" | tee -a /tmp/append.txt

echo "File contents:"
cat /tmp/append.txt
rm /tmp/append.txt

echo ""
echo "=== Multiple Files ==="

# Write to multiple files at once
echo "Multi-destination" | tee /tmp/file1.txt /tmp/file2.txt /tmp/file3.txt

echo "file1: $(cat /tmp/file1.txt)"
echo "file2: $(cat /tmp/file2.txt)"
echo "file3: $(cat /tmp/file3.txt)"
rm /tmp/file{1,2,3}.txt

echo ""
echo "=== tee in Pipeline ==="

# Log intermediate results
seq 1 5 | tee /tmp/original.txt | grep -v 3 | tee /tmp/filtered.txt | wc -l

echo "Original: $(cat /tmp/original.txt | tr '\n' ' ')"
echo "Filtered: $(cat /tmp/filtered.txt | tr '\n' ' ')"
rm /tmp/original.txt /tmp/filtered.txt

echo ""
echo "=== tee with Process Substitution ==="

# Multiple processing paths
echo "Process me" | tee >(tr 'a-z' 'A-Z') >(rev)
sleep 0.1

echo ""
echo "=== tee stderr ==="

# Capture stderr while showing it
{
    ls /tmp /nonexistent
} 2>&1 | tee /tmp/all_output.txt | grep -i "no such"

echo "Full output saved to file"
rm /tmp/all_output.txt

echo ""
echo "=== Practical: Log and Process ==="

generate_data() {
    for i in {1..5}; do
        echo "Record $i: $(date +%s)"
        sleep 0.1
    done
}

# Log everything, but only show odd records
generate_data | tee /tmp/all_records.log | grep -E "Record [135]"

echo ""
echo "Log contains $(wc -l < /tmp/all_records.log) records"
rm /tmp/all_records.log

echo ""
echo "=== Silent tee (no terminal output) ==="

# Write to file only, suppress terminal
echo "Secret data" | tee /tmp/secret.txt > /dev/null
echo "Data saved but not shown"
echo "File: $(cat /tmp/secret.txt)"
rm /tmp/secret.txt

echo ""
echo "=== tee with sudo ==="

# Can't do: sudo echo "text" > /protected/file
# Do: echo "text" | sudo tee /protected/file

echo "To write to protected file:"
echo 'echo "text" | sudo tee /etc/myconfig > /dev/null'
Output: === Basic tee === Hello World File contains: Hello World === Append with tee === Line 1 Line 2 Line 3 File contents: Line 1 Line 2 Line 3 === Multiple Files === Multi-destination file1: Multi-destination file2: Multi-destination file3: Multi-destination === tee in Pipeline === 4 Original: 1 2 3 4 5 Filtered: 1 2 4 5 === tee with Process Substitution === Process me PROCESS ME em ssecorP === Practical: Log and Process === Record 1: 1732012245 Record 3: 1732012245 Record 5: 1732012246 Log contains 5 records === tee with sudo === To write to protected file: echo "text" | sudo tee /etc/myconfig > /dev/null
7
Reading User Input

Various ways to read input from users and files.

#!/bin/bash
# Reading user input

echo "=== Basic read ==="

# Read into variable (simulated)
echo "John" | { read name; echo "Hello, $name"; }

echo ""
echo "=== read with Prompt ==="

# -p for prompt (simulated)
echo "Alice" | { read -p "Name: " name; echo "Got: $name"; }

echo ""
echo "=== read Multiple Variables ==="

echo "one two three four" | {
    read a b c
    echo "a=$a b=$b c=$c"  # c gets "three four"
}

echo ""
echo "=== read with Delimiter ==="

# -d sets delimiter
echo -n "a:b:c" | {
    read -d ':' first
    echo "First field: $first"
}

echo ""
echo "=== read Character by Character ==="

# -n reads N characters
echo "Hello" | {
    read -n 1 char
    echo "First char: $char"
}

echo ""
echo "=== read with Timeout ==="

# -t sets timeout in seconds
if read -t 1 -p "Quick! " answer < /dev/null; then
    echo "Got: $answer"
else
    echo "Timed out"
fi

echo ""
echo "=== read into Array ==="

echo "one two three" | {
    read -a arr
    echo "Array: ${arr[*]}"
    echo "Second element: ${arr[1]}"
}

echo ""
echo "=== Silent read (passwords) ==="

# -s for silent (no echo)
# Simulated:
echo "Passwords should use: read -s -p 'Password: ' pass"

echo ""
echo "=== read Lines from File ==="

# Create test file
cat > /tmp/data.txt << 'EOF'
apple
banana  
cherry
EOF

echo "Reading file line by line:"
while IFS= read -r line; do
    echo "  > $line"
done < /tmp/data.txt

echo ""
echo "=== read with Custom IFS ==="

# Parse CSV
echo "name,age,city" | {
    IFS=',' read -r f1 f2 f3
    echo "Field 1: $f1"
    echo "Field 2: $f2"
    echo "Field 3: $f3"
}

echo ""
echo "=== read from Here-String ==="

read -r word1 word2 <<< "hello world"
echo "Words: $word1, $word2"

echo ""
echo "=== Practical: Menu Selection ==="

show_menu() {
    cat << 'EOF'
1) Option A
2) Option B
3) Quit
EOF
}

# Simulated selection
echo "2" | {
    show_menu
    read -p "Select: " choice
    case $choice in
        1) echo "Selected A" ;;
        2) echo "Selected B" ;;
        3) echo "Goodbye" ;;
        *) echo "Invalid" ;;
    esac
}

echo ""
echo "=== read Preserving Whitespace ==="

echo "  indented  " | {
    # Without IFS=
    read line1
    echo "Default: '$line1'"
}

echo "  indented  " | {
    # With IFS=
    IFS= read -r line2
    echo "IFS=: '$line2'"
}

rm /tmp/data.txt
Output: === Basic read === Hello, John === read Multiple Variables === a=one b=two c=three four === read with Delimiter === First field: a === read Character by Character === First char: H === read with Timeout === Timed out === read into Array === Array: one two three Second element: two === read Lines from File === Reading file line by line: > apple > banana > cherry === read with Custom IFS === Field 1: name Field 2: age Field 3: city === read from Here-String === Words: hello, world === read Preserving Whitespace === Default: 'indented' IFS=: ' indented '
8
Capturing Command Output

Different ways to capture command output in variables.

#!/bin/bash
# Capturing command output

echo "=== Command Substitution $(cmd) ==="

# Basic capture
today=$(date +%Y-%m-%d)
echo "Today: $today"

# Capture with arguments
files=$(ls /tmp | wc -l)
echo "Files in /tmp: $files"

echo ""
echo "=== Backticks (legacy) ==="

# Old style - avoid
count=`echo "hello" | wc -c`
echo "Characters: $count"

# Problems with backticks:
# - Hard to nest
# - Backslash handling differs

echo ""
echo "=== Nested Command Substitution ==="

# Easy with $()
result=$(echo "Inner: $(date +%H:%M)")
echo "$result"

echo ""
echo "=== Capture Multi-line Output ==="

output=$(cat << 'EOF'
Line 1
Line 2
Line 3
EOF
)

echo "Captured:"
echo "$output"

echo ""
echo "=== Capture stdout Only ==="

# stderr goes to terminal
result=$(ls /tmp /nonexistent 2>&1)
echo "Got: $result"

echo ""
echo "=== Capture stderr Only ==="

# Swap and capture
result=$( { ls /nonexistent; } 2>&1 1>/dev/null )
echo "Error: $result"

echo ""
echo "=== Capture Both Separately ==="

{
    output=$(ls /tmp /nonexistent 2>&1 1>&3)
} 3>&1

echo "stdout in output variable"
echo "stderr: $output"

echo ""
echo "=== Capture Exit Status ==="

output=$(false)
status=$?
echo "Output: '$output', Status: $status"

# Or in one line
if output=$(ls /nonexistent 2>&1); then
    echo "Success: $output"
else
    echo "Failed: $output"
fi

echo ""
echo "=== Capture to Array ==="

# Each line as array element
readarray -t lines < <(echo -e "one\ntwo\nthree")
echo "Lines: ${#lines[@]}"
echo "Second: ${lines[1]}"

echo ""
echo "=== Practical: Parse Command Output ==="

# Get specific field
mem_free=$(free -m | awk '/^Mem:/ {print $4}')
echo "Free memory: ${mem_free}MB"

# Get multiple values
read -r user system idle <<< $(top -bn1 | grep "Cpu(s)" | awk -F',' '{print $1,$2,$4}' | grep -oE '[0-9.]+')
echo "CPU - User: $user%, System: $system%"

echo ""
echo "=== mapfile/readarray ==="

# Read lines into array
mapfile -t items < <(echo -e "apple\nbanana\ncherry")
echo "Items: ${items[*]}"

# With callback
mapfile -t -C 'echo "Read: "' -c 1 nums < <(seq 1 3)

echo ""
echo "=== printf -v (no subshell) ==="

# Capture without forking
printf -v formatted "Value: %05d" 42
echo "$formatted"
Output: === Command Substitution $(cmd) === Today: 2025-11-20 Files in /tmp: 12 === Nested Command Substitution === Inner: 10:30 === Capture Multi-line Output === Captured: Line 1 Line 2 Line 3 === Capture Exit Status === Output: '', Status: 1 Failed: ls: cannot access '/nonexistent': No such file or directory === Capture to Array === Lines: 3 Second: two === Practical: Parse Command Output === Free memory: 2048MB CPU - User: 5.2%, System: 2.1% === printf -v (no subshell) === Value: 00042
9
Advanced Redirection Patterns

Complex redirection scenarios for scripts.

#!/bin/bash
# Advanced redirection patterns

echo "=== Redirect for Entire Script ==="

# All output to log
# exec > /tmp/script.log 2>&1

echo ""
echo "=== Conditional Redirection ==="

verbose=true

if $verbose; then
    exec 3>&1  # Save stdout
else
    exec 3>/dev/null  # Discard
fi

echo "Always shown"
echo "Only if verbose" >&3

exec 3>&-

echo ""
echo "=== Redirect Block Output ==="

{
    echo "Line 1"
    echo "Line 2"
    echo "Line 3"
} > /tmp/block.txt

echo "Block output:"
cat /tmp/block.txt
rm /tmp/block.txt

echo ""
echo "=== noclobber Protection ==="

# Prevent accidental overwrite
set -o noclobber

echo "test" > /tmp/protected.txt

# This would fail:
# echo "overwrite" > /tmp/protected.txt

# Force overwrite with >|
echo "forced" >| /tmp/protected.txt

set +o noclobber
rm /tmp/protected.txt

echo "noclobber demonstrated"

echo ""
echo "=== Redirect within Function ==="

log_to_file() {
    local logfile=$1
    shift
    
    "$@" >> "$logfile" 2>&1
}

log_to_file /tmp/func.log echo "Logged message"
log_to_file /tmp/func.log date
echo "Log:"
cat /tmp/func.log
rm /tmp/func.log

echo ""
echo "=== Coproc for Two-way Communication ==="

# Start coprocess
coproc bc

# Send calculation
echo "5 + 3" >&${COPROC[1]}

# Read result
read result <&${COPROC[0]}
echo "5 + 3 = $result"

# Close
exec {COPROC[1]}>&-

echo ""
echo "=== exec for Permanent Redirection ==="

# Save original stdout
exec 3>&1

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

echo "This goes to file"
date

# Restore stdout
exec 1>&3
exec 3>&-

echo "Restored to terminal"
echo "File:"
cat /tmp/exec_test.txt
rm /tmp/exec_test.txt

echo ""
echo "=== Discard Specific File Descriptor ==="

# Close stdin to prevent reads
exec 0</dev/null

# Script can't read input now
# But can still write output
echo "stdin closed for this script"

echo ""
echo "=== Practical: Output Filtering ==="

# Suppress specific patterns
{
    echo "INFO: Starting"
    echo "DEBUG: Details"
    echo "INFO: Done"
} | grep -v DEBUG

echo ""
echo "=== Create Empty File ==="

# Truncate/create
> /tmp/empty.txt
ls -l /tmp/empty.txt
rm /tmp/empty.txt
Output: === Redirect for Entire Script === === Conditional Redirection === Always shown Only if verbose === Redirect Block Output === Block output: Line 1 Line 2 Line 3 === noclobber Protection === noclobber demonstrated === Redirect within Function === Log: Logged message Thu Nov 20 10:30:45 UTC 2025 === Coproc for Two-way Communication === 5 + 3 = 8 === exec for Permanent Redirection === Restored to terminal File: This goes to file Thu Nov 20 10:30:45 UTC 2025 === Practical: Output Filtering === INFO: Starting INFO: Done === Create Empty File === -rw-r--r-- 1 user user 0 Nov 20 10:30 /tmp/empty.txt
10
Complete I/O Library

Reusable functions for common I/O operations.

#!/bin/bash
# Complete I/O library

#######################################
# Logging Functions
#######################################

LOG_FILE=""
LOG_LEVEL=1  # 0=ERROR, 1=INFO, 2=DEBUG

setup_logging() {
    LOG_FILE=$1
    exec 3>>"$LOG_FILE"
}

_log() {
    local level=$1
    local msg=$2
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $msg" >&3
}

log_error() { _log "ERROR" "$1"; echo "ERROR: $1" >&2; }
log_info()  { ((LOG_LEVEL >= 1)) && _log "INFO" "$1"; }
log_debug() { ((LOG_LEVEL >= 2)) && _log "DEBUG" "$1"; }

close_logging() {
    exec 3>&-
}

#######################################
# File I/O
#######################################

safe_write() {
    local file=$1
    local content=$2
    local temp="${file}.tmp.$$"
    
    if echo "$content" > "$temp"; then
        mv "$temp" "$file"
        return 0
    else
        rm -f "$temp"
        return 1
    fi
}

append_line() {
    local file=$1
    local line=$2
    echo "$line" >> "$file"
}

read_file() {
    local file=$1
    [[ -r "$file" ]] && cat "$file"
}

#######################################
# User Input
#######################################

prompt() {
    local msg=$1
    local default=$2
    local response
    
    if [[ -n "$default" ]]; then
        read -p "$msg [$default]: " response
        echo "${response:-$default}"
    else
        read -p "$msg: " response
        echo "$response"
    fi
}

confirm() {
    local msg=$1
    local response
    
    read -p "$msg (y/n): " response
    [[ "$response" =~ ^[Yy] ]]
}

#######################################
# Output Formatting
#######################################

print_header() {
    local title=$1
    local width=${2:-40}
    
    printf '%*s\n' "$width" '' | tr ' ' '='
    printf '%*s\n' $(((width + ${#title}) / 2)) "$title"
    printf '%*s\n' "$width" '' | tr ' ' '='
}

print_kv() {
    local key=$1
    local value=$2
    printf "%-20s : %s\n" "$key" "$value"
}

#######################################
# Stream Processing
#######################################

filter_lines() {
    local pattern=$1
    grep -E "$pattern"
}

transform_lines() {
    local cmd=$1
    while IFS= read -r line; do
        eval "$cmd"
    done
}

#######################################
# Demo
#######################################

echo ""
print_header "I/O Library Demo" 50

echo ""
echo "=== Logging ==="
setup_logging "/tmp/demo.log"
LOG_LEVEL=2

log_info "Application started"
log_debug "Debug information"
log_error "An error occurred"

close_logging
echo "Log file:"
cat /tmp/demo.log
rm /tmp/demo.log

echo ""
echo "=== Safe Write ==="
safe_write /tmp/safe.txt "Content written safely"
echo "File: $(cat /tmp/safe.txt)"
rm /tmp/safe.txt

echo ""
echo "=== Output Formatting ==="
print_kv "Name" "John Doe"
print_kv "Age" "30"
print_kv "City" "New York"

echo ""
echo "=== Stream Processing ==="
echo -e "apple\nbanana\ncherry" | filter_lines "^[ab]"

echo ""
echo "=== Transform Lines ==="
echo -e "hello\nworld" | transform_lines 'echo "${line^^}"'

print_header "Demo Complete" 50
Output: ================================================== I/O Library Demo ================================================== === Logging === ERROR: An error occurred Log file: [2025-11-20 10:30:45] [INFO] Application started [2025-11-20 10:30:45] [DEBUG] Debug information [2025-11-20 10:30:45] [ERROR] An error occurred === Safe Write === File: Content written safely === Output Formatting === Name : John Doe Age : 30 City : New York === Stream Processing === apple banana === Transform Lines === HELLO WORLD ================================================== Demo Complete ==================================================

Quick Reference

Essential Operators

# Output
cmd > file          # stdout to file (overwrite)
cmd >> file         # stdout to file (append)
cmd 2> file         # stderr to file
cmd &> file         # both to file
cmd >&2             # stdout to stderr

# Input
cmd < file          # stdin from file
cmd <<< "string"    # here-string
cmd << EOF          # here-document

# Pipes
cmd1 | cmd2         # pipe stdout
cmd1 |& cmd2        # pipe both

# Process substitution
<(cmd)              # command as input file
>(cmd)              # command as output file

Common Patterns

TaskCode
Discard outputcmd > /dev/null
Discard errorscmd 2> /dev/null
Discard allcmd &> /dev/null
Log everythingcmd 2>&1 | tee log.txt
Write to stderrecho "error" >&2
Capture outputvar=$(cmd)
Avoid subshellwhile read; done < <(cmd)
✓ Best Practices
✗ Common Mistakes
← Back to BashSpecial Index ↑ Back to EXPANDED