14.2. Here-Documents#
14.2.1. Common Pitfalls#
1. Variable expansion in quoted heredocs
# Bad: Variables don't expand with 'EOF'
cat << 'EOF'
User: $USER
Home: $HOME
EOF
# Output: User: $USER (literal)
# Good: Use unquoted delimiter
cat << EOF
User: $USER
Home: $HOME
EOF
# Output: User: alice (expanded)
2. Indentation with heredoc
# Bad: Leading spaces are part of content
if true; then
cat << 'EOF'
First line
Second line
EOF
fi
# Output has leading spaces
# Good: Use <<- with tabs
if true; then
cat <<- 'EOF'
First line
Second line
EOF
fi
# Leading tabs removed, but only tabs (not spaces)
3. Special characters in here-strings
# Bad: Unquoted string breaks parsing
grep "error" <<< $error_message # May break if $error_message has spaces
# Good: Quote the variable
grep "error" <<< "$error_message"
4. Escape sequences in heredocs
# Heredocs don't interpret escape sequences
cat << 'EOF'
Line 1\nLine 2 # Outputs literal \n, not newline
EOF
# Use echo -e or explicit newlines
echo -e "Line 1\nLine 2"
# Or:
cat << 'EOF'
Line 1
Line 2
EOF
5. Mixing quotes and expansion
# Bad: Variable with special chars breaks
password="a'b\"c"
cat << EOF
password=$password # May break
EOF
# Good: Escape or avoid
cat << 'EOF'
password='a'"'"'b"c' # Properly escaped
EOF
# Or just use:
password='a'"'"'b"c'
cat << EOF
password=$password
EOF
14.2.2. Here-Strings and Advanced String Handling#
14.2.2.1. Here-Strings (<<<)#
#!/bin/bash
# Here-string: pass string as stdin to command
wc -w <<< "hello world from bash" # Output: 4
# Parse variables
name="Alice"
grep "$name" <<< "Alice Bob Charlie
Alice Smith
Dave"
# Here-string to variable
response=$(cat <<< "OK")
# Process multi-line string
data="line1
line2
line3"
# Count lines
wc -l <<< "$data"
# Filter with while
read -r first_line rest <<< "the rest of the line"
echo "First: $first_line"
echo "Rest: $rest"
14.2.2.2. String Manipulation#
#!/bin/bash
# Remove patterns (used in heredocs)
text="The quick brown fox"
# Remove prefix
echo "${text#The }" # quick brown fox
# Remove suffix
echo "${text%fox}" # The quick brown
# Replace all occurrences
echo "${text//o/_}" # The quick brwn f_x
# Extract substring
echo "${text:4:5}" # quick
# Length
echo "${#text}" # 19
# Case conversion (Bash 4+)
echo "${text^^}" # THE QUICK BROWN FOX
echo "${text,,}" # the quick brown fox
# Trim whitespace
text=" hello world "
text="${text#"${text%%[![:space:]]*}"}" # Trim leading
text="${text%"${text##*[![:space:]]}"}" # Trim trailing
14.2.2.3. Formatting with Here-Documents#
#!/bin/bash
# Create formatted output
print_table() {
local -a headers=("ID" "Name" "Status")
local -a rows=(
"1:Alice:Active"
"2:Bob:Inactive"
"3:Charlie:Active"
)
# Create table with heredoc
{
# Headers
printf "%-5s %-10s %-10s\n" "${headers[@]}"
printf "%0.s=" {1..25}
echo
# Rows
while IFS=: read -r id name status; do
printf "%-5s %-10s %-10s\n" "$id" "$name" "$status"
done <<< "$(printf '%s\n' "${rows[@]}")"
}
}
print_table
# Email composition
compose_email() {
local recipient=$1
local subject=$2
cat << EOF | sendmail "$recipient"
Subject: $subject
To: $recipient
From: system@example.com
$(date) - Automated notification
This is an automated email.
Please do not reply to this address.
Best regards,
System Administrator
EOF
}
14.2.3. Here-Documents (Heredoc)#
Here-documents allow embedding multi-line text directly in scripts, essential for templates and config generation.
14.2.3.1. Basic Heredoc Syntax#
#!/bin/bash
# Simple heredoc
cat << 'EOF'
This is a multi-line string
All lines between << 'EOF' and EOF are included literally
Variables are NOT expanded because of quotes around EOF
EOF
# Heredoc with variable expansion
TITLE="My Report"
cat << EOF
Report Title: $TITLE
Generated: $(date)
Current User: $USER
EOF
# Heredoc to variable
content=$(cat << 'EOF'
Line 1: apple
Line 2: banana
Line 3: cherry
EOF
)
echo "$content"
# Heredoc with indentation (using <<- DELIMITER)
function format_config() {
cat <<- 'EOF'
[database]
host = localhost
port = 5432
user = postgres
EOF
}
# Note: <<- allows leading tabs (not spaces) in the delimiter
14.2.3.2. Heredoc to Commands and Files#
#!/bin/bash
# Heredoc to command (stdin)
grep "error" << 'EOF'
warning: disk space low
error: connection failed
info: process started
error: timeout expired
EOF
# Outputs only error lines
# Heredoc to file
cat > /tmp/config.ini << 'EOF'
[settings]
debug=true
timeout=30
log_level=info
EOF
# Heredoc to multiple files
{
cat > file1.txt << 'EOF'
Content for file 1
EOF
cat > file2.txt << 'EOF'
Content for file 2
EOF
}
# Heredoc with pipes
cat << 'EOF' | while IFS=: read -r user pass uid gid name home shell; do
echo "User: $user (UID: $uid)"
done
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33::/var/www:/usr/sbin/nologin
EOF
14.2.3.3. Template Generation#
#!/bin/bash
# Generate HTML from heredoc
TITLE="Status Report"
TIMESTAMP=$(date)
cat > report.html << EOF
<!DOCTYPE html>
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>Generated: $TIMESTAMP</p>
<p>Hostname: $(hostname)</p>
<ul>
<li>CPU Cores: $(nproc)</li>
<li>Memory: $(free -h | grep Mem | awk '{print \$2}')</li>
</ul>
</body>
</html>
EOF
# Generate SQL script
USER="backup"
TABLES="users,posts,comments"
cat > backup.sql << EOF
-- Backup script
-- Generated: $(date)
-- User: $(whoami)
FLUSH PRIVILEGES;
LOCK TABLES;
$(echo "$TABLES" | tr ',' '\n' | while read table; do
echo "BACKUP TABLE $table TO '/backups/$table';"
done)
UNLOCK TABLES;
EOF