Bash Heredoc: Complete Guide with Examples

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:
[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
EOForENDare 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.
- You can use any string as a delimiting identifier, though
- 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 >:
$ cat << EOF
> Hello
> World
> EOF
Hello
WorldIn Zsh
, the default shell on macOS, the prompt reads heredoc> instead:
% cat << EOF
heredoc> Hello
heredoc> World
heredoc> EOF
Hello
WorldThe 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`
cat << EOF
The current working directory is: $PWD
You are logged in as: $(whoami)
EOFThe output shows the expanded variable and command substitution:
The current working directory is: /home/linuxize
You are logged in as: linuxizeQuoted Delimiter (Literal Mode)
Quoting the delimiter with single or double quotes disables all shell expansions:
cat << 'EOF'
The current working directory is: $PWD
You are logged in as: $(whoami)
EOFThe 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.
#!/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:
<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.
<<- 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:
cat << EOF > /tmp/myfile.txt
The current working directory is: $PWD
You are logged in as: $(whoami)
EOFIf 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:
sudo tee /etc/myconfig.conf << EOF
setting1=value1
setting2=value2
EOFPiping 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:
cat <<'EOF' | sed 's/l/e/g'
Hello
World
EOFHeeeo
WoredTo write the piped data to a file:
cat <<'EOF' | sed 's/l/e/g' > file.txt
Hello
World
EOFAssigning a Heredoc to a Variable
To store a multi-line string in a variable, wrap the heredoc in a command substitution:
message=$(cat << EOF
Hello $USER,
Your home directory is $HOME.
EOF
)
echo "$message"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:
IFS= read -r -d '' message << 'EOF'
Hello $USER,
Your home directory is $HOME.
EOF
printf '%s' "$message"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:
ssh user@host.com 'bash -s' << EOF
echo "Local working directory: $PWD"
echo "Remote working directory: \$PWD"
EOFLocal working directory: /home/linuxize
Remote working directory: /home/userThe 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:
ssh user@host.com 'bash -s' << 'EOF'
echo "Remote working directory: $PWD"
echo "Remote user: $(whoami)"
EOFBest 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 -sas 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:
command <<< "string"For example, to pass a string to grep:
grep "linux" <<< "I love linux and bash"I love linux and bashHerestrings are useful when you need to pass a variable or short string to a command without using echo and a pipe:
# Using herestring
tr '[:lower:]' '[:upper:]' <<< "hello world"
# Equivalent using echo and pipe
echo "hello world" | tr '[:lower:]' '[:upper:]'Both commands output:
HELLO WORLDPractical 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:
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;
}
}
EOFNote 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:
sudo mysql << 'EOF'
CREATE DATABASE myapp;
USE myapp;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
);
EOFThe 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:
cat << EOF > data.json
{
"name": "$USER",
"home": "$HOME",
"shell": "$SHELL"
}
EOFCommon Mistakes
Spaces Before Closing Delimiter
The closing delimiter must be at the beginning of a line with no leading whitespace:
# Wrong - spaces before EOF
cat << EOF
Hello World
EOFMove the closing delimiter to the beginning of the line:
# Correct
cat << EOF
Hello World
EOFMissing 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:
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:
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:
echo << EOF
Hello World
EOFThe 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:
# Wrong - indented with spaces (not stripped)
if true; then
cat <<- EOF
Hello
EOF
fiWith spaces, Bash does not recognize the indented EOF as the closing delimiter, and the script fails with a syntax error. Use literal tabs instead:
# Correct - indented with tabs
if true; then
cat <<- EOF
Hello
EOF
fiUnintended Variable Expansion
Variables in heredocs are expanded by default, which can cause unexpected results:
cat << EOF
The price is $5.00
EOFThe price is .00Bash 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:
# 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
EOFQuick Reference
For a printable quick reference, see the Bash cheatsheet .
| Syntax | Description |
|---|---|
<< EOF | Heredoc with variable expansion |
<< 'EOF' | Heredoc without variable expansion (literal) |
<< "EOF" | Same as quoted single quotes (literal) |
<<- EOF | Heredoc 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 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