Sagar.BlogArticle
All posts
All posts
Bash

Here Documents and Here Strings

Here documents (<<EOF) and here strings (<<<) let you pass multi-line text to commands without temp files. Essential for config generation, SQL, and long command inputs.

January 20, 20266 min read
BashHeredocInputScripting

A here document (heredoc) is a way to provide multi-line input to a command directly in the script, without creating a temporary file. It's cleaner than multiple echo statements and essential for generating config files, running SQL queries, or producing formatted output.

Basic Heredoc — <<DELIMITER

# Feed multi-line text to a command
cat <<EOF
This is line 1
This is line 2
Variables ARE expanded: $USER
Command substitution works: $(date +%F)
EOF

# The delimiter can be any word — EOF is conventional, but not required
cat <<END
  Hello from heredoc
END

The closing delimiter must be alone

The closing EOF (or whatever delimiter you chose) must be on its own line with no leading spaces or trailing characters. Even a trailing space makes Bash not recognize it as the end of the heredoc.

No Expansion — <<'EOF'

Quoting the delimiter with single quotes prevents all variable and command expansion — useful for writing scripts, Dockerfiles, awk programs, or anything that has its own $:

cat <<'EOF'
No expansion here: $USER $(date)
This goes into the file literally, including $PATH
Useful for writing other scripts or code
EOF

Indented Heredoc — <<-

<<-EOF strips leading tabs (not spaces) from each line and the delimiter. This lets you indent the heredoc body to match the surrounding code.

function show_help {
    cat <<-EOF
	Usage: myscript [OPTIONS] <file>
	Options:
	  -v    Verbose output
	  -h    Show this help
	EOF
}
# Note: the body lines use TABs, not spaces, for indentation

Practical Uses

#!/usr/bin/env bash

# 1. Generate a config file
cat > /etc/myapp/config.conf <<EOF
host=${DB_HOST:-localhost}
port=${DB_PORT:-5432}
name=${DB_NAME:-mydb}
generated=$(date)
EOF

# 2. Run SQL
psql "$DATABASE_URL" <<EOF
SELECT id, name, email
FROM users
WHERE active = true
ORDER BY created_at DESC
LIMIT 10;
EOF

# 3. Create a script file
cat > /usr/local/bin/cleanup <<'EOF'
#!/usr/bin/env bash
find /tmp -mtime +7 -delete
echo "Cleanup done"
EOF
chmod +x /usr/local/bin/cleanup

# 4. Feed to ssh (run remote commands)
ssh user@server <<EOF
  cd /var/www/app
  git pull
  systemctl restart nginx
EOF

Here String — <<<

# Feed a single string to stdin
grep "root" <<< "$(cat /etc/passwd)"

# Calculate with bc
result=$(bc <<< "scale=4; sqrt(144)")
echo "$result"    # 12.0000

# Parse values with read
IFS=, read -r name age city <<< "Alice,30,NYC"
echo "Name: $name, Age: $age, City: $city"
Quick Check

What does `cat <<'EOF'` do differently from `cat <<EOF`?

Exercise

Write a script that generates a simple Nginx config file at /tmp/nginx-test.conf. Use a heredoc with variable expansion to fill in:

  • A server_name from variable DOMAIN (default: "example.com")
  • A root path from variable WEBROOT (default: "/var/www/html")

Then print the file contents.