Comprehensive Reference Guide — File descriptors, pipes, here-docs, tee, and advanced I/O patterns
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.
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
| Operator | Description | Example |
|---|---|---|
> |
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 |
| Operator | Description | Example |
|---|---|---|
< |
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) |
| Operator | Description | Example |
|---|---|---|
| |
Pipe stdout to stdin | ls | grep txt |
|& |
Pipe both stdout and stderr | cmd |& grep error |
cmd > file 2>&1 redirects both to file, but cmd 2>&1 > file only redirects stdout to file (stderr goes to original stdout).
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
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
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
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
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")
< <(cmd) instead of cmd | when you need to preserve variable changes in the current shell. Pipes create subshells.
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'
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
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"
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
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
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
| Task | Code |
|---|---|
| Discard output | cmd > /dev/null |
| Discard errors | cmd 2> /dev/null |
| Discard all | cmd &> /dev/null |
| Log everything | cmd 2>&1 | tee log.txt |
| Write to stderr | echo "error" >&2 |
| Capture output | var=$(cmd) |
| Avoid subshell | while read; done < <(cmd) |
2>&1 after > to capture both streamsIFS= read -r to preserve whitespace$( ) instead of backtickscmd 2>&1 > file - stderr still goes to terminal"$(cmd)"> on files you want to append to