Bash Heredoc: Complete Guide with Examples

By 

•

Updated on

•

10 min read

Bash Heredoc

When writing shell scripts, you may need to pass a multiline block of text or code to an interactive command such as tee , cat, or sftp .

In Bash and other shells like Zsh, a Here document (Heredoc) is a type of redirection that allows you to pass multiple lines of input directly to a command.

Heredocs are commonly used for:

  • Generating configuration files
  • Passing scripts to remote servers via SSH
  • Automating interactive commands
  • Writing readable multi-line strings in shell scripts

Heredoc Syntax

The basic heredoc syntax looks like this:

txt
[COMMAND] <<[-] 'DELIMITER'
  HERE-DOCUMENT
DELIMITER
  • The first line starts with an optional command followed by the special redirection operator << and the delimiting identifier.
    • You can use any string as a delimiting identifier, though EOF or END are the most common.
    • If the delimiting identifier is unquoted, the shell will substitute all variables, commands and special characters before passing the here-document lines to the command.
    • Appending a minus sign to the redirection operator <<- causes all leading tab characters to be ignored. This allows you to use indentation when writing heredocs in shell scripts.
  • The here-document block can contain strings, variables, commands and any other type of input.
  • The last line ends with the delimiting identifier. White space in front of or after the delimiter is not allowed. The only exception is leading tabs when you use <<-.

Heredoc is most often used in combination with the cat command .

Heredoc in the Terminal

You can also type a heredoc directly at the shell prompt. After you press Enter on the first line, the shell waits for more input and shows a secondary prompt on each new line. In Bash, the prompt is >:

output
$ cat << EOF
> Hello
> World
> EOF
Hello
World

In Zsh , the default shell on macOS, the prompt reads heredoc> instead:

output
% cat << EOF
heredoc> Hello
heredoc> World
heredoc> EOF
Hello
World

The heredoc> prompt means the shell is still collecting lines for the here-document. To finish, type the delimiter on a line by itself and press Enter. To cancel the command without running it, press Ctrl+C.

Variable and Command Expansion

Unquoted Delimiter (Default Behavior)

If the delimiter is not quoted, the shell performs:

  • Variable expansion ($VAR)
  • Command substitution ($(command))
  • Backslash handling for characters such as \, $, and `
sh
cat << EOF
The current working directory is: $PWD
You are logged in as: $(whoami)
EOF

The output shows the expanded variable and command substitution:

output
The current working directory is: /home/linuxize
You are logged in as: linuxize

Quoted Delimiter (Literal Mode)

Quoting the delimiter with single or double quotes disables all shell expansions:

sh
cat << 'EOF'
The current working directory is: $PWD
You are logged in as: $(whoami)
EOF
output
The current working directory is: $PWD
You are logged in as: $(whoami)

When the delimiter is quoted, no parameter expansion or command substitution is performed by the shell.

This is useful for configuration files, scripts, or regex patterns where you want to prevent accidental variable substitution.

Indented Heredocs with <<-

When using heredocs inside functions, loops, or conditionals, indentation improves readability. The <<- operator strips leading tab characters (not spaces) from each line of the heredoc, including the closing delimiter.

sh
#!/bin/bash

generate_page() {
	cat <<-EOF
	<html>
	<head>
	    <title>$1</title>
	</head>
	<body>
	    <h1>$1</h1>
	</body>
	</html>
	EOF
}

generate_page "My Page"

Running the script prints the HTML without the leading tabs:

output
<html>
<head>
    <title>My Page</title>
</head>
<body>
    <h1>My Page</h1>
</body>
</html>

The <<- operator removes every leading tab, not only the first one. If you indent the nested lines with a second tab, that indentation is stripped too. To keep nested indentation in the output, use a tab for the code level and spaces for the content, as the <title> and <h1> lines do in the example above.

Warning
The <<- operator only strips tabs, not spaces. If your editor converts tabs to spaces, the heredoc will not work as expected.

Redirecting Heredoc Output to a File

Instead of displaying the output on the screen, you can redirect it to a file using the > or >> operators:

sh
cat << EOF > /tmp/myfile.txt
The current working directory is: $PWD
You are logged in as: $(whoami)
EOF

If the file does not exist, it will be created. Using > overwrites the file, while >> appends to it.

You can also use tee to write to a file that requires root permissions:

sh
sudo tee /etc/myconfig.conf << EOF
setting1=value1
setting2=value2
EOF

Piping Heredoc Content

Heredoc input can be piped to other commands for further processing.

In the following example, the sed command replaces all instances of l with e:

sh
cat <<'EOF' | sed 's/l/e/g'
Hello
World
EOF
output
Heeeo
Wored

To write the piped data to a file:

sh
cat <<'EOF' | sed 's/l/e/g' > file.txt
Hello
World
EOF

Assigning a Heredoc to a Variable

To store a multi-line string in a variable, wrap the heredoc in a command substitution:

sh
message=$(cat << EOF
Hello $USER,
Your home directory is $HOME.
EOF
)

echo "$message"
output
Hello linuxize,
Your home directory is /home/linuxize.

The closing parenthesis goes on the line after the delimiter. Quote the variable when you print it ("$message"). Without the quotes, the shell joins all lines into one.

Command substitution removes all trailing newlines from the output, even when you quote the variable.

You can also use the read built-in. Setting IFS= preserves leading and trailing whitespace. The -d '' option makes read stop at a NUL character instead of a newline, so it reads the whole heredoc:

sh
IFS= read -r -d '' message << 'EOF'
Hello $USER,
Your home directory is $HOME.
EOF

printf '%s' "$message"
output
Hello $USER,
Your home directory is $HOME.

This time the delimiter is quoted, so $USER and $HOME are stored literally. The printf '%s' command prints the stored text without adding another newline.

Be careful with this form in scripts that use set -e. When read reaches the end of the input, it returns a non-zero exit status, and set -e stops the script.

Using Heredoc with SSH

Heredocs are useful for executing multiple commands on a remote server over SSH .

When using an unquoted delimiter, escape variables that should be evaluated on the remote server:

sh
ssh user@host.com 'bash -s' << EOF
echo "Local working directory: $PWD"
echo "Remote working directory: \$PWD"
EOF
output
Local working directory: /home/linuxize
Remote working directory: /home/user

The first $PWD is expanded locally before the heredoc is sent, while \$PWD is escaped and evaluated on the remote server.

The bash -s argument tells the remote shell to read the commands from standard input. Without it, ssh starts a login shell. On Ubuntu servers, the login shell prints the full welcome message (MOTD) before your output. It also prints Pseudo-terminal will not be allocated because stdin is not a terminal. unless you add the -T option.

To prevent all local expansion, quote the delimiter:

sh
ssh user@host.com 'bash -s' << 'EOF'
echo "Remote working directory: $PWD"
echo "Remote user: $(whoami)"
EOF

Best practices when using heredoc with SSH:

  • Use quoted delimiters when all commands should run on the remote server
  • Escape variables (\$VAR) that should be evaluated remotely when using unquoted delimiters
  • Pass bash -s as the remote command to keep the login banner out of the output
  • Consider SSH key-based authentication for automation

Herestring

A herestring is a simpler form of heredoc that passes a single line (or a short string) to a command’s standard input. It uses the <<< operator:

txt
command <<< "string"

For example, to pass a string to grep:

sh
grep "linux" <<< "I love linux and bash"
output
I love linux and bash

Herestrings are useful when you need to pass a variable or short string to a command without using echo and a pipe:

sh
# Using herestring
tr '[:lower:]' '[:upper:]' <<< "hello world"

# Equivalent using echo and pipe
echo "hello world" | tr '[:lower:]' '[:upper:]'

Both commands output:

output
HELLO WORLD

Practical Examples

Writing a Configuration File

The following example writes a basic Nginx server block . The DOMAIN variable is set first, so the shell can expand it inside the heredoc:

sh
DOMAIN=example.com

cat << EOF > /tmp/nginx.conf
server {
    listen 80;
    server_name $DOMAIN;
    root /var/www/$DOMAIN/html;

    location / {
        try_files \$uri \$uri/ =404;
    }
}
EOF

Note that $DOMAIN is expanded locally, while $uri is escaped to be written literally.

Running SQL Queries

You can pass several SQL statements to the mysql client in one heredoc. On Ubuntu, the MySQL root user authenticates through the auth_socket plugin by default, so run the client with sudo:

sh
sudo mysql << 'EOF'
CREATE DATABASE myapp;
USE myapp;
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100)
);
EOF

The delimiter is quoted, so the shell passes the statements to MySQL unchanged.

Generating JSON

The next example builds a small JSON file from environment variables:

sh
cat << EOF > data.json
{
    "name": "$USER",
    "home": "$HOME",
    "shell": "$SHELL"
}
EOF

Common Mistakes

Spaces Before Closing Delimiter

The closing delimiter must be at the beginning of a line with no leading whitespace:

sh
# Wrong - spaces before EOF
cat << EOF
Hello World
  EOF

Move the closing delimiter to the beginning of the line:

sh
# Correct
cat << EOF
Hello World
EOF

Missing or Mistyped Closing Delimiter

If Bash does not find a line that matches the delimiter exactly, the heredoc continues to the end of the file. A single trailing space is enough to cause this. In the following script, the closing EOF line ends with a space:

script.shsh
cat << EOF
Hello World
EOF 
echo "This line never runs"

When you run the script, Bash prints a warning and treats the rest of the file as heredoc text:

output
script.sh: line 4: warning: here-document at line 1 delimited by end-of-file (wanted `EOF')
Hello World
EOF 
echo "This line never runs"

The echo line is printed as text, not executed. The same warning appears when the closing delimiter is missing or misspelled. If you see it, check the delimiter line for typos and stray whitespace.

Using echo Instead of cat

The echo command does not read standard input. It prints only its arguments, so a heredoc passed to echo is ignored:

sh
echo << EOF
Hello World
EOF

The command prints an empty line. Use cat to print heredoc text, or tee to write it to a file and the screen at the same time.

Using Spaces Instead of Tabs with <<-

The <<- operator only strips tabs, not spaces:

sh
# Wrong - indented with spaces (not stripped)
if true; then
    cat <<- EOF
    Hello
    EOF
fi

With spaces, Bash does not recognize the indented EOF as the closing delimiter, and the script fails with a syntax error. Use literal tabs instead:

sh
# Correct - indented with tabs
if true; then
	cat <<- EOF
	Hello
	EOF
fi

Unintended Variable Expansion

Variables in heredocs are expanded by default, which can cause unexpected results:

sh
cat << EOF
The price is $5.00
EOF
output
The price is .00

Bash reads $5 as the fifth positional parameter. It is not set, so it expands to an empty string.

Fix by escaping the dollar sign or quoting the delimiter:

sh
# Option 1: Escape the dollar sign
cat << EOF
The price is \$5.00
EOF

# Option 2: Quote the delimiter
cat << 'EOF'
The price is $5.00
EOF

Quick Reference

For a printable quick reference, see the Bash cheatsheet .

SyntaxDescription
<< EOFHeredoc with variable expansion
<< 'EOF'Heredoc without variable expansion (literal)
<< "EOF"Same as quoted single quotes (literal)
<<- EOFHeredoc with leading tabs stripped
var=$(cat << EOF ...)Store a heredoc in a variable
ssh host 'bash -s' << 'EOF'Run a heredoc on a remote server
<<< "string"Herestring (single line input)

Conclusion

Heredocs are a powerful tool for embedding multi-line input directly in your scripts. Quote the delimiter when you need literal text, leave it unquoted when you need variable expansion, and use <<- with tabs to keep indented scripts readable. To learn how to run your scripts, see how to run a bash script .

Tags

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page