Skip to content
Go back

Here Documents vs Here Strings in Bash

By SumGuy 5 min read
Here Documents vs Here Strings in Bash

You want to pass a multiline config or SQL statement to a command in bash. You could use echo with escapes and multiple lines, which is unreadable garbage. Or you could use a here document, which is clean and readable.

Here documents (<<EOF) and here strings (<<<) are bash features that solve the “pass text to a command” problem. Most people don’t know they exist. Here’s the thing: once you use them, you’ll never go back to chaining echo commands.

Here Documents: The Basics

A here document passes multiline text to a command:

simple_heredoc.sh
#!/bin/bash
cat <<EOF
This is line 1
This is line 2
This is line 3
EOF
Terminal window
$ bash simple_heredoc.sh
This is line 1
This is line 2
This is line 3

The <<EOF starts the here document. Everything until the closing EOF is passed as input to cat. No escaping needed.

You can redirect to any command:

heredoc_to_command.sh
#!/bin/bash
wc -l <<EOF
Line 1
Line 2
Line 3
EOF
Terminal window
$ bash heredoc_to_command.sh
3

Or to a file:

heredoc_to_file.sh
#!/bin/bash
cat > /tmp/config.txt <<EOF
server {
listen 80;
server_name example.com;
}
EOF
Terminal window
$ bash heredoc_to_file.sh
$ cat /tmp/config.txt
server {
listen 80;
server_name example.com;
}

The Indentation Problem: <<-EOF

Here documents usually start at column 0. If your script is indented (inside a function or if block), it looks weird:

indent_problem.sh
#!/bin/bash
if true; then
cat <<EOF
This text
starts at column 0
EOF
fi

The closing EOF has to be at column 0, which breaks indentation. Use <<-EOF instead:

indent_fixed.sh
#!/bin/bash
if true; then
cat <<-EOF
This text
can be indented
EOF
fi

With <<-, the closing delimiter can be indented (tabs are stripped). Much cleaner.

Suppressing Variable Expansion

By default, variables in here documents are expanded:

expand_vars.sh
#!/bin/bash
name="Alice"
cat <<EOF
Hello $name
EOF
Terminal window
$ bash expand_vars.sh
Hello Alice

If you want literal $name, quote the opening delimiter:

no_expand.sh
#!/bin/bash
name="Alice"
cat <<'EOF'
Hello $name
EOF
Terminal window
$ bash no_expand.sh
Hello $name

The quotes tell bash: don’t expand variables in this here document.

Piping Here Documents

Pipe the output to another command:

pipe_heredoc.sh
#!/bin/bash
cat <<EOF | grep "test"
This is a test
Not a match
Another test
EOF
Terminal window
$ bash pipe_heredoc.sh
This is a test
Another test

Or send to a command that reads stdin:

pipe_to_command.sh
#!/bin/bash
mysql -u root -p <<EOF
CREATE DATABASE newdb;
USE newdb;
CREATE TABLE users (id INT, name VARCHAR(255));
EOF

No interactive prompts, clean SQL.

Here Strings: Single-Line Version

For single lines, use here strings (<<<):

heredoc_string.sh
#!/bin/bash
wc -w <<<'hello world test'
Terminal window
$ bash heredoc_string.sh
3

That’s equivalent to:

Terminal window
$ echo 'hello world test' | wc -w
3

But cleaner. No forking an echo process.

With variables:

heredoc_string_var.sh
#!/bin/bash
text="hello world"
grep "hello" <<<$text

Here strings are useful for passing data to commands that expect input without spawning echo.

Real-World Example 1: Config File Generation

generate_config.sh
#!/bin/bash
domain="example.com"
email="admin@example.com"
cat > /etc/nginx/sites-available/$domain <<-EOF
server {
listen 80;
server_name $domain;
location / {
proxy_pass http://localhost:3000;
}
# Auto-generated: $(date)
# Maintainer: $email
}
EOF
echo "Config created for $domain"

The config is readable, variables are interpolated, and it’s all in one block.

Real-World Example 2: Database Setup

setup_db.sh
#!/bin/bash
DB_USER="app"
DB_PASS="secure_password"
DB_NAME="myapp"
mysql -u root -p<<-EOF
CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';
CREATE DATABASE $DB_NAME;
GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';
FLUSH PRIVILEGES;
EOF
echo "Database and user created"

Clean, readable SQL. No escape nightmare.

Real-World Example 3: Multiline Variables

Store multiline text in a variable:

multiline_var.sh
#!/bin/bash
readme=$(cat <<-EOF
# My Project
This is a readme file.
It has multiple lines.
All stored in a variable.
EOF
)
echo "$readme"
Terminal window
$ bash multiline_var.sh
# My Project
This is a readme file.
It has multiple lines.
All stored in a variable.

Difference: EOF vs Other Delimiters

You don’t have to use EOF. Any word works:

custom_delimiter.sh
#!/bin/bash
cat <<SCRIPT
#!/bin/bash
echo "This is bash code"
SCRIPT

Use a different delimiter if the content contains EOF:

delimiter_choice.sh
#!/bin/bash
cat <<'MARKER'
This contains EOF but won't be confused
with the actual EOF delimiter
MARKER

Suppressing Trailing Newline

By default, here documents add a trailing newline. To avoid it, use read:

no_newline.sh
#!/bin/bash
result=$(cat <<EOF
no newline at end
EOF
)
# But this adds a newline from the variable expansion
# To truly remove it, use read -d ''
read -d '' result <<EOF
no newline
EOF
echo "Result length: ${#result}"

Usually not worth the complexity, but it’s there if you need it.

The Pattern: Use Heredocs for Readability

BAD:

Terminal window
echo "server {" > config.txt
echo " listen 80;" >> config.txt
echo " server_name example.com;" >> config.txt
echo "}" >> config.txt

GOOD:

Terminal window
cat > config.txt <<-EOF
server {
listen 80;
server_name example.com;
}
EOF

The second is readable. The first is unmaintainable garbage.

The One Thing

Next time you want to write multiline text in bash, use:

template.sh
#!/bin/bash
# For multiline with variable expansion
cat <<-EOF
Your content here
Variable: $VAR
Can span lines
EOF
# For multiline without expansion
cat <<-'EOF'
Your content here
Literal: $VAR (not expanded)
Can span lines
EOF
# For single line
command <<<'your input here'

That’s your toolkit. Use it, and your scripts become readable instead of noise.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it may appear here.


Previous Post
Open Source Security: Scanning Your Dependencies Before They Scan You
Next Post
HandBrake and Video Transcoding: Your Media Library Deserves Better Compression

Related Posts