Here Documents and Here Strings in Bash
September 24, 2026
Here documents and here strings let you embed text directly in your scripts without external files. They are especially useful for generating configuration files, feeding multi-line input to commands, and writing scripts that are entirely self-contained.
Here Documents
A here document (heredoc) sends a block of text to a command's standard input. The syntax uses << followed by a delimiter word. Everything between the delimiter and its closing occurrence becomes the input.
cat <<EOF
Hello from a here document.
This is line two.
EOF
Output:
Hello from a here document.
This is line two.
The delimiter can be any word. EOF is conventional, but you can use anything as long as the opening and closing match exactly.
cat <<END_OF_MESSAGE
This works too.
END_OF_MESSAGE
Writing Files with Heredocs
This is the most common use case. Instead of running multiple echo commands or maintaining a separate template file, embed the content directly in your script.
cat <<EOF > /etc/myapp/config.ini
[database]
host = localhost
port = 3306
name = production
[logging]
level = info
file = /var/log/myapp.log
EOF
The > redirect creates the file with the heredoc contents. Use >> to append instead.
Variable Expansion
By default, heredocs expand variables, command substitutions, and escape sequences just like double-quoted strings.
name="web-01"
ip="203.0.113.50"
cat <<EOF
Host: $name
IP: $ip
Date: $(date '+%Y-%m-%d')
Kernel: $(uname -r)
EOF
Output:
Host: web-01
IP: 203.0.113.50
Date: 2026-09-24
Kernel: 6.12.41-gentoo
Preventing Expansion
If you want the text passed through literally without any expansion, quote the delimiter.
cat <<'EOF'
This $variable is not expanded.
Neither is $(this command).
Backslashes are literal too: \n \t
EOF
Output:
This $variable is not expanded.
Neither is $(this command).
Backslashes are literal too: \n \t
This is essential when generating shell scripts or configuration files that contain their own variables.
cat <<'SCRIPT' > /usr/local/bin/backup.sh
#!/bin/bash
BACKUP_DIR="/var/backups/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
rsync -a /data/ "$BACKUP_DIR/"
SCRIPT
chmod 755 /usr/local/bin/backup.sh
Without the quotes around SCRIPT, the $BACKUP_DIR and $(date) would be expanded when the outer script runs, which is not what you want.
Indented Heredocs with <<-
In scripts with deeply nested logic, you might want to indent the heredoc body to match the surrounding code. The <<- syntax strips leading tabs (not spaces) from the heredoc content.
if [[ "$deploy" == "true" ]]; then
cat <<-EOF
Deploying to production.
Host: $hostname
Time: $(date)
EOF
fi
Note that you must use actual tab characters for the indentation, not spaces. Many editors can be configured to insert tabs when you need them.
Piping Heredocs
You can pipe a heredoc into any command, not just cat.
# Count lines
wc -l <<EOF
line one
line two
line three
EOF
# Feed SQL to a database client
mysql -u root mydatabase <<EOF
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE
);
EOF
# Send a formatted email
sendmail user@example.com <<EOF
Subject: Disk Usage Alert
From: root@$(hostname)
Disk usage on $(hostname) has exceeded 90%.
$(df -h /)
EOF
Heredocs with sudo
A common task is writing a file to a root-owned location from a script. Since > redirect happens in the current shell (not as root), you need tee with sudo.
sudo tee /etc/cron.d/cleanup > /dev/null <<EOF
# Clean up temp files daily at 3am
0 3 * * * root find /tmp -type f -mtime +7 -delete
EOF
The > /dev/null suppresses tee printing to stdout.
Here Strings
A here string sends a single string to a command's standard input. It uses <<< followed by the string.
wc -w <<< "count the words in this string"
Output:
6
When to Use Here Strings
Here strings are useful when you need to feed a short value to a command that reads from stdin.
Parsing values without echo piping:
# Instead of this
echo "user:admin:1000" | cut -d: -f2
# Use a here string
cut -d: -f2 <<< "user:admin:1000"
Reading into variables:
IFS=: read -r user role uid <<< "john:admin:1000"
echo "User: $user, Role: $role, UID: $uid"
String operations with grep or sed:
version="nginx/1.24.0"
if grep -q "1.24" <<< "$version"; then
echo "Running nginx 1.24"
fi
# Extract version number
sed 's|.*/||' <<< "$version" # 1.24.0
Base64 encoding without echo:
base64 <<< "encode this string"
Variables in Here Strings
Here strings expand variables, just like double-quoted strings.
greeting="hello world"
wc -c <<< "$greeting" # 12 (includes trailing newline)
To prevent expansion, use single quotes.
wc -c <<< '$greeting' # 10 (literal $greeting plus newline)
Practical Example: Server Provisioning Script
Here is a snippet from a provisioning script that uses heredocs to set up configuration files on a new server.
#!/bin/bash
set -euo pipefail
hostname="$1"
# Set the hostname
echo "$hostname" > /etc/hostname
# Generate network config
cat <<EOF > /etc/conf.d/net
config_eth0="dhcp"
dns_servers_eth0="203.0.113.1"
EOF
# Generate sshd config (literal - no expansion needed)
cat <<'EOF' > /etc/ssh/sshd_config.d/hardened.conf
PermitRootLogin prohibit-password
PasswordAuthentication no
X11Forwarding no
MaxAuthTries 3
EOF
# Generate motd with server info (expansion needed)
cat <<EOF > /etc/motd
========================================
$hostname
Provisioned: $(date '+%Y-%m-%d %H:%M')
========================================
EOF
echo "Server $hostname configured"
The key decision is whether to quote the delimiter. If the content contains variables or commands that belong to the target file, quote it. If the content should include values from the current script's context, leave it unquoted.
Here documents and here strings eliminate the need for temporary files and keep your scripts self-contained. Once you are comfortable with the quoting rules, they become an indispensable part of your shell scripting toolkit.