Heredoc Patterns Reference

Overview

Heredocs (Here Documents) allow you to pass multi-line strings to commands without escaping. Master these patterns for efficient shell scripting.

Basic Syntax

Pattern Behavior Use Case

<<EOF

Variables expanded, commands executed

Dynamic content

<<'EOF'

Literal string, no expansion

Scripts, configs with $ symbols

<←EOF

Strips leading tabs (not spaces)

Indented scripts

<<<

Here-string (single line)

Quick single-line input

Pattern 1: Execute Multiple Commands

bash <<'EOF'
cd ~/atelier/_bibliotheca/domus-siem-ops
git remote remove origin
git remote add origin git@github.com:EvanusModestus/domus-siem-ops.git
git remote add gitlab git@gitlab.com:EvanusModestus/domus-siem-ops.git
git remote add gitea git@gitea-01.inside.domusdigitalis.dev:evanusmodestus/domus-siem-ops.git
git remote -v
EOF

Pattern 2: Git Commit with Heredoc

git commit -m "$(cat <<'EOF'
feat(siem): Add QRadar threat detection queries

- Add lateral movement detection (SMB, RDP, WinRM)
- Add data exfiltration queries
- Add authentication monitoring
- All queries use attributes for environment values
EOF
)"

Pattern 3: Create Configuration Files

sudo tee /etc/NetworkManager/conf.d/wifi_backend.conf > /dev/null <<'EOF'
[device]
wifi.backend=wpa_supplicant
EOF

Pattern 4: Create Scripts On-The-Fly

cat > /tmp/quick-audit.sh <<'EOF'
#!/bin/bash
echo "=== System Audit ==="
echo "Hostname: $(hostname)"
echo "Uptime: $(uptime -p)"
echo "Disk: $(df -h / | tail -1)"
echo "Memory: $(free -h | grep Mem)"
EOF
chmod +x /tmp/quick-audit.sh

Pattern 5: SSH Remote Execution

ssh user@server <<'EOF'
cd /var/log
grep -i error syslog | tail -20
systemctl status nginx
df -h
EOF

Pattern 6: Conditional Heredoc

cat <<EOF
Server: ${SERVER:-localhost}
Port: ${PORT:-8080}
Debug: ${DEBUG:-false}
EOF

Pattern 7: Append to File

cat >> ~/.zshrc <<'EOF'

# domus-siem-ops alias
alias dsiem='cd ~/atelier/_bibliotheca/domus-siem-ops'
alias domus-siem='cd ~/atelier/_bibliotheca/domus-siem-ops'
EOF

Pattern 8: JSON Payload

curl -X POST https://api.example.com/endpoint \
  -H "Content-Type: application/json" \
  -d "$(cat <<'EOF'
{
  "name": "domus-siem-ops",
  "description": "Vendor-agnostic SIEM operations",
  "private": true,
  "auto_init": false
}
EOF
)"

Pattern 9: Multi-Command Pipeline

cat <<'EOF' | while read -r cmd; do
  echo "Running: $cmd"
  eval "$cmd"
done
git status --short
git log --oneline -5
git remote -v
EOF

Pattern 10: AQL Query Execution

# Store query in heredoc, execute via API
QUERY=$(cat <<'EOF'
SELECT
    sourceip AS "Source",
    destinationip AS "Destination",
    COUNT(*) AS "Connections"
FROM flows
WHERE destinationport = 445
GROUP BY sourceip, destinationip
ORDER BY "Connections" DESC
LAST 24 HOURS
EOF
)

curl -sk "https://$QRADAR_HOST/api/ariel/searches" \
  -H "SEC: $QRADAR_TOKEN" \
  -d "query_expression=$QUERY"

Pattern 11: Database Operations

psql -U postgres <<'EOF'
SELECT username, last_login, failed_attempts
FROM users
WHERE failed_attempts > 5
ORDER BY failed_attempts DESC;
EOF

Pattern 12: Netapi Bulk Operations

# Create multiple ISE objects
bash <<'EOF'
dsource d000 dev/network

# Create dACL
netapi ise create-dacl -n "Linux-AD-Auth" --acl "permit udp any any eq 53
permit tcp any any eq 88
permit tcp any any eq 389
permit tcp any any eq 636
deny ip any 10.0.0.0/8"

# Create authorization profile
netapi ise create-authz-profile -n "Linux-AD-Auth-Profile" \
  --dacl "Linux-AD-Auth" \
  --vlan "DATA_VLAN"

echo "Done creating ISE objects"
EOF

Pattern 13: Indented with Tab Stripping

if [[ "$DEPLOY" == "true" ]]; then
    cat <<-EOF
	Deploying to production...
	Server: $SERVER
	Version: $VERSION
	EOF
fi
Use actual tabs (not spaces) for <←EOF stripping to work.

Pattern 14: Here-String (Single Line)

# Quick single-line input
grep "error" <<< "$LOG_CONTENT"

# Convert to uppercase
tr '[:lower:]' '[:upper:]' <<< "hello world"

# JSON parsing
jq '.name' <<< '{"name": "domus-siem-ops"}'

Pattern 15: Function with Heredoc

create_config() {
    local hostname=$1
    local ip=$2

    cat <<EOF
[server]
hostname = $hostname
ip_address = $ip
port = 8443
ssl = true
EOF
}

# Usage
create_config "ise-01" "10.50.1.20" > /tmp/server.conf

Anti-Patterns (Avoid)

# BAD: Unquoted EOF with variables you don't want expanded
cat <<EOF
Password: $PASSWORD  # This will be empty or wrong!
EOF

# GOOD: Quote EOF
cat <<'EOF'
Password: $PASSWORD  # Literal $PASSWORD
EOF

# BAD: Mixing tabs and spaces with <<-
# GOOD: Use only tabs for indentation with <<-

Quick Reference Card

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    HEREDOC QUICK REFERENCE                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Execute commands:     bash <<'EOF' ... EOF                  β”‚
β”‚ Git commit:           git commit -m "$(cat <<'EOF' ... EOF)"β”‚
β”‚ Create file:          cat > file <<'EOF' ... EOF            β”‚
β”‚ Append to file:       cat >> file <<'EOF' ... EOF           β”‚
β”‚ Tee with sudo:        sudo tee file <<'EOF' ... EOF         β”‚
β”‚ SSH remote:           ssh user@host <<'EOF' ... EOF         β”‚
β”‚ Curl JSON:            curl -d "$(cat <<'EOF' ... EOF)"      β”‚
β”‚ Strip tabs:           <<-EOF (use actual tabs)              β”‚
β”‚ Single line:          <<< "string"                          β”‚
β”‚ Variable expansion:   <<EOF (unquoted)                      β”‚
β”‚ Literal/no expand:    <<'EOF' (quoted)                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜