Skip to content

Here_docs

Chapter 19: Here Documents and Here Strings

Section titled “Chapter 19: Here Documents and Here Strings”

Here Documents (<<) and Here Strings (<<<) are powerful features in Bash that allow you to pass multi-line text or strings directly to commands without using external files. This chapter covers these features in detail with practical DevOps examples.


A Here Document redirects input from within the script itself:

Terminal window
command << delimiter
document
delimiter
┌─────────────────────────────────────────────────────────┐
│ Here Document Flow │
├─────────────────────────────────────────────────────────┤
│ │
│ Script Command Input │
│ ────── ───────────── │
│ │
│ <<EOF ┌─────────────────┐ │
│ line 1 ───────────────► │ Command reads │ │
│ line 2 ───────────────► │ lines as │ │
│ line 3 ───────────────► │ stdin │ │
│ EOF └─────────────────┘ │
│ │
│ - Delimiter marks start and end │
│ - All content between delimiters is passed │
│ - No temporary files needed │
│ │
└─────────────────────────────────────────────────────────┘

Terminal window
# Create nginx configuration
cat > /tmp/nginx.conf <<EOF
server {
listen 80;
server_name example.com;
root /var/www/html;
location / {
index index.html;
}
location /api {
proxy_pass http://localhost:3000;
}
}
EOF
Terminal window
# Run SQL commands
mysql -u root -p <<EOF
CREATE DATABASE IF NOT EXISTS app_db;
USE app_db;
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (username, email) VALUES
('admin', 'admin@example.com'),
('john', 'john@example.com');
EOF
#!/usr/bin/env bash
# Generate HTML report
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
HOSTNAME=$(hostname)
cat > /tmp/report.html <<EOF
<!DOCTYPE html>
<html>
<head>
<title>System Report</title>
<style>
body { font-family: Arial, sans-serif; }
.header { background: #333; color: white; padding: 20px; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>System Report</h1>
<p>Generated: $TIMESTAMP</p>
<p>Host: $HOSTNAME</p>
</div>
<div class="content">
<h2>System Information</h2>
<pre>
$(uname -a)
</pre>
<h2>Memory Usage</h2>
<pre>
$(free -h)
</pre>
</div>
</body>
</html>
EOF
echo "Report generated: /tmp/report.html"

The - option removes leading tabs for better formatting:

Terminal window
# Without dash - indentation causes issues
cat <<EOF
line with spaces
EOF
# With dash - leading tabs are stripped
cat <<-EOF
line with indentation
more lines
EOF
# This is useful for readable scripts:
if true; then
cat <<-EOF
This text has consistent indentation
But it won't appear in the output
EOF
fi

Prevent variable and command expansion:

Terminal window
# Without quotes - variables expanded
name="World"
cat <<EOF
Hello $name
$(date)
EOF
# Output:
# Hello World
# Sat Feb 22 12:00:00 2025
# With quotes - literal text
name="World"
cat <<"EOF"
Hello $name
$(date)
EOF
# Output:
# Hello $name
# $(date)
# With single quotes (same as double)
cat <<'EOF'
Hello $name
$(date)
EOF
Terminal window
# Create file with here doc
cat > /path/to/file.txt <<EOF
Line 1
Line 2
Line 3
EOF
# Append to file
cat >> /path/to/file.txt <<EOF
Line 4
Line 5
EOF

Terminal window
command <<< "string"
┌────────────────────────────────────────┐
│ Here String Flow │
├────────────────────────────────────────┤
│ │
│ "hello world" ───────────────► │
│ command │
│ │
│ - Single string (no newlines) │
│ - Word splitting occurs │
│ - Can contain multiple words │
│ │
└────────────────────────────────────────┘
Terminal window
# Echo a string
echo <<< "Hello World"
# Read string into variable
read -r name <<< "John Doe"
echo "$name" # John Doe
# Use with other commands
wc -w <<< "one two three four five" # 5
# Base64 encode
base64 <<< "secretpassword"
# Hash a string
sha256sum <<< "password"
Terminal window
# Split into array
read -ra words <<< "one two three four"
echo "${words[0]}" # one
echo "${words[2]}" # three
# Use with awk
echo <<< "100 200 300" | awk '{print $1 + $2}'
# Use with bc
echo <<< "10 + 5" | bc
#!/usr/bin/env bash
# Validate email using here string
validate_email() {
local email="$1"
local regex="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if [[ $email =~ $regex ]]; then
echo "Valid email"
return 0
else
echo "Invalid email"
return 1
fi
}
validate_email "user@example.com"

FeatureHere DocumentHere String
Syntax<<EOF<<<"string"
ContentMulti-lineSingle line
VariablesExpandableExpandable
Use caseMulti-line inputSingle string
PerformanceSlowerFaster

#!/usr/bin/env bash
# Create SSH config
SSH_CONFIG="$HOME/.ssh/config"
cat >> "$SSH_CONFIG" <<EOF
# Generated by setup script
Host server1
HostName 192.168.1.10
User ubuntu
IdentityFile ~/.ssh/id_rsa
StrictHostKeyChecking no
Host server2
HostName 192.168.1.20
User admin
IdentityFile ~/.ssh/id_rsa
EOF
chmod 600 "$SSH_CONFIG"
#!/usr/bin/env bash
# Create Docker Compose file
cat > docker-compose.yml <<EOF
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=postgres
depends_on:
- postgres
- redis
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=appdb
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
EOF
#!/usr/bin/env bash
# Generate Kubernetes deployment
cat > deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: myregistry/my-app:latest
ports:
- containerPort: 8080
env:
- name: ENV
value: "production"
resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
EOF
#!/usr/bin/env bash
# Generate Terraform main.tf
cat > main.tf <<EOF
terraform {
required_version = ">= 1.0"
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "WebServer"
Environment = "Production"
ManagedBy = "Terraform"
}
}
resource "aws_security_group" "web_sg" {
name = "web-sg"
description = "Security group for web servers"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
EOF
#!/usr/bin/env bash
# Create multiple system users
USERS=("alice" "bob" "charlie")
for user in "${USERS[@]}"; do
# Create user with here document
useradd -m -s /bin/bash "$user"
# Set password
chpasswd <<EOF
${user}:password123
EOF
# Add to group
usermod -aG developers "$user"
# Create .bashrc
cat > "/home/$user/.bashrc" <<EOF
# .bashrc for $user
export PS1="[\u@\h \W]\$ "
alias ll='ls -la'
alias gs='git status'
export EDITOR=vim
EOF
echo "User $user created"
done

Terminal window
# ❌ Wrong - spaces will appear in output
cat << EOF
indented text
EOF
# ✓ Correct - use tabs
cat <<-EOF
indented text
EOF
Terminal window
# ❌ Wrong - delimiter must be at start of line
cat <<EOF
text
EOF
# ✓ Correct - delimiter at beginning
cat <<EOF
text
EOF
Terminal window
# ❌ Forgetting to quote
cat <<EOF
Hello $name
EOF
# ✓ Always quote when needed
cat <<"EOF"
Hello $name
EOF

Terminal window
# Here doc - slower (creates subshell)
result=$(cat <<EOF
$variable
more text
EOF
)
# Here string - faster
result=$(<<< "$variable
more text")
Terminal window
# ❌ Subshell for each line
while read line; do
process "$line"
done <<< "$(cat file.txt)"
# ✓ Direct input
while read line; do
process "$line"
done < file.txt

In this chapter, you learned:

  • ✅ Here Documents basics and syntax
  • ✅ Creating configuration files dynamically
  • ✅ Database SQL scripts
  • ✅ Here Document features (tabs, quoting)
  • ✅ Here Strings for single-line input
  • ✅ Advanced examples for DevOps
  • ✅ Common pitfalls and performance tips

Continue to the next chapter to learn about Regular Expressions in Bash.


Previous Chapter: File Operations Next Chapter: Regular Expressions