Here_docs
Chapter 19: Here Documents and Here Strings
Section titled “Chapter 19: Here Documents and Here Strings”Overview
Section titled “Overview”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.
Here Documents (Here Docs)
Section titled “Here Documents (Here Docs)”Basic Syntax
Section titled “Basic Syntax”A Here Document redirects input from within the script itself:
command << delimiter documentdelimiterHow It Works
Section titled “How It Works”┌─────────────────────────────────────────────────────────┐│ 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 ││ │└─────────────────────────────────────────────────────────┘Common Use Cases
Section titled “Common Use Cases”1. Creating Configuration Files
Section titled “1. Creating Configuration Files”# Create nginx configurationcat > /tmp/nginx.conf <<EOFserver { listen 80; server_name example.com; root /var/www/html;
location / { index index.html; }
location /api { proxy_pass http://localhost:3000; }}EOF2. Database SQL Scripts
Section titled “2. Database SQL Scripts”# Run SQL commandsmysql -u root -p <<EOFCREATE 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');EOF3. Generating Dynamic Content
Section titled “3. Generating Dynamic Content”#!/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"Here Document Features
Section titled “Here Document Features”Removing Leading Tabs (-)
Section titled “Removing Leading Tabs (-)”The - option removes leading tabs for better formatting:
# Without dash - indentation causes issuescat <<EOF line with spacesEOF
# With dash - leading tabs are strippedcat <<-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 EOFfiQuoting the Delimiter
Section titled “Quoting the Delimiter”Prevent variable and command expansion:
# Without quotes - variables expandedname="World"cat <<EOFHello $name$(date)EOF# Output:# Hello World# Sat Feb 22 12:00:00 2025
# With quotes - literal textname="World"cat <<"EOF"Hello $name$(date)EOF# Output:# Hello $name# $(date)
# With single quotes (same as double)cat <<'EOF'Hello $name$(date)EOFWriting to a File
Section titled “Writing to a File”# Create file with here doccat > /path/to/file.txt <<EOFLine 1Line 2Line 3EOF
# Append to filecat >> /path/to/file.txt <<EOFLine 4Line 5EOFHere Strings (Here Strings)
Section titled “Here Strings (Here Strings)”Basic Syntax
Section titled “Basic Syntax”command <<< "string"How It Works
Section titled “How It Works”┌────────────────────────────────────────┐│ Here String Flow │├────────────────────────────────────────┤│ ││ "hello world" ───────────────► ││ command ││ ││ - Single string (no newlines) ││ - Word splitting occurs ││ - Can contain multiple words ││ │└────────────────────────────────────────┘Common Examples
Section titled “Common Examples”1. String to Command
Section titled “1. String to Command”# Echo a stringecho <<< "Hello World"
# Read string into variableread -r name <<< "John Doe"echo "$name" # John Doe
# Use with other commandswc -w <<< "one two three four five" # 5
# Base64 encodebase64 <<< "secretpassword"
# Hash a stringsha256sum <<< "password"2. Parsing Strings
Section titled “2. Parsing Strings”# Split into arrayread -ra words <<< "one two three four"echo "${words[0]}" # oneecho "${words[2]}" # three
# Use with awkecho <<< "100 200 300" | awk '{print $1 + $2}'
# Use with bcecho <<< "10 + 5" | bc3. Input Validation
Section titled “3. Input Validation”#!/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"Comparison: Here Doc vs Here String
Section titled “Comparison: Here Doc vs Here String”| Feature | Here Document | Here String |
|---|---|---|
| Syntax | <<EOF | <<<"string" |
| Content | Multi-line | Single line |
| Variables | Expandable | Expandable |
| Use case | Multi-line input | Single string |
| Performance | Slower | Faster |
Advanced Examples
Section titled “Advanced Examples”1. SSH Key Generation
Section titled “1. SSH Key Generation”#!/usr/bin/env bash# Create SSH config
SSH_CONFIG="$HOME/.ssh/config"
cat >> "$SSH_CONFIG" <<EOF
# Generated by setup scriptHost 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_rsaEOF
chmod 600 "$SSH_CONFIG"2. Docker Configuration
Section titled “2. Docker Configuration”#!/usr/bin/env bash# Create Docker Compose file
cat > docker-compose.yml <<EOFversion: '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:EOF3. Kubernetes Manifest
Section titled “3. Kubernetes Manifest”#!/usr/bin/env bash# Generate Kubernetes deployment
cat > deployment.yaml <<EOFapiVersion: apps/v1kind: Deploymentmetadata: name: my-app labels: app: my-appspec: 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: v1kind: Servicemetadata: name: my-app-servicespec: selector: app: my-app ports: - port: 80 targetPort: 8080 type: LoadBalancerEOF4. Terraform Configuration
Section titled “4. Terraform Configuration”#!/usr/bin/env bash# Generate Terraform main.tf
cat > main.tf <<EOFterraform { 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"] }}EOF5. User Creation Script
Section titled “5. User Creation Script”#!/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}:password123EOF
# Add to group usermod -aG developers "$user"
# Create .bashrc cat > "/home/$user/.bashrc" <<EOF# .bashrc for $userexport PS1="[\u@\h \W]\$ "alias ll='ls -la'alias gs='git status'export EDITOR=vimEOF
echo "User $user created"doneCommon Pitfalls
Section titled “Common Pitfalls”1. Indentation Issues
Section titled “1. Indentation Issues”# ❌ Wrong - spaces will appear in outputcat << EOF indented textEOF
# ✓ Correct - use tabscat <<-EOF indented textEOF2. Delimiter Positioning
Section titled “2. Delimiter Positioning”# ❌ Wrong - delimiter must be at start of linecat <<EOF text EOF
# ✓ Correct - delimiter at beginningcat <<EOF textEOF3. Variable Expansion
Section titled “3. Variable Expansion”# ❌ Forgetting to quotecat <<EOFHello $nameEOF
# ✓ Always quote when neededcat <<"EOF"Hello $nameEOFPerformance Tips
Section titled “Performance Tips”Use Here Strings When Possible
Section titled “Use Here Strings When Possible”# Here doc - slower (creates subshell)result=$(cat <<EOF$variablemore textEOF)
# Here string - fasterresult=$(<<< "$variablemore text")Avoid Unnecessary Subshells
Section titled “Avoid Unnecessary Subshells”# ❌ Subshell for each linewhile read line; do process "$line"done <<< "$(cat file.txt)"
# ✓ Direct inputwhile read line; do process "$line"done < file.txtSummary
Section titled “Summary”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
Next Steps
Section titled “Next Steps”Continue to the next chapter to learn about Regular Expressions in Bash.
Previous Chapter: File Operations Next Chapter: Regular Expressions