In Bash scripting, a "here document" (or "heredoc") redirects multiple lines of input to a command or program, as if you were typing them directly into the terminal. It's a powerful tool for embedding multi-line text within your scripts without needing external files or complex string manipulation.
Delimiter: a heredoc starts with the <<
operator followed by a delimiter word (often called the "marker" or "terminator").
This delimiter can be any word you choose, but it's common to use something like EOF
, END
, or TEXT
for clarity.
For more readable code, you can use something descriptive as the delimiter, for example END_INSTALLATION_INSTRUCTIONS
.
Content: after the initial << DELIMITER
, you write the content you want to redirect.
This can be multiple lines of text, code, or anything else.
Termination: the heredoc ends when the delimiter word appears again on a line by itself, with no leading or trailing whitespace.
command << DELIMITER
Content line 1
Content line 2
...
Content line N
DELIMITER
<< DELIMITER
and the ending DELIMITER
.cat << EOF
This is the first line.
This is the second line.
This is the third line.
EOF
Output:
This is the first line.
This is the second line.
This is the third line.
In this example:
cat
is the command.<< EOF
starts the heredoc with EOF
as the delimiter.EOF
on its own line ends the heredoc.cat
then outputs the content it received.wc -l << END
Line 1
Line 2
Line 3
END
Output:
3
Here, wc -l
counts the number of lines.
The heredoc provides the three lines as input.
The script:
#!/usr/bin/env bash
# Script to process input
while IFS= read -r line; do
echo "Processing: $line"
done
Call the script from an interactive bash prompt with a heredoc:
./your_script << MY_DATA
Item 1
Item 2
Item 3
MY_DATA
Output:
Processing: Item 1
Processing: Item 2
Processing: Item 3
Bash performs variable expansion, command substitution, and arithmetic expansion within a heredoc. In this sense, heredocs act like double quoted strings.
cat << EOF
The value of HOME is $HOME
The current date is $(date)
Two plus two is $((2 + 2))
EOF
Output:
The value of HOME is /home/glennj
The current date is Thu Apr 24 13:47:32 EDT 2025
Two plus two is 4
When the delimiter is quoting (using single or double quotes), these expansions are prevented. The content is taken literally. This is like single quoted strings.
cat << 'EOF'
The value of $HOME is not expanded here.
The result of $(date) is not executed.
Two plus two is calculated by $((2 + 2))
EOF
Output:
The value of $HOME is not expanded here.
The result of $(date) is not executed.
Two plus two is calculated by $((2 + 2))
If you use <<-
(with a trailing hyphen) instead of <<
, Bash will strip any leading tab characters from each line of the heredoc.
This is useful for indenting the heredoc content within your script without affecting the output.
# Note, the leading whitespace is tab characters only, not spaces!
# The ending delimiter can have leading tabs as well.
cat <<- END
This line has 1 leading tab.
This line has a leading tab and some spaces.
This line 2 leading tabs.
END
The output is printed with all the leading tabs removed:
This line has 1 leading tab.
This line has a leading tab and some spaces.
This line 2 leading tabs.
The author doesn't recommend this usage. While it can improve the readability of the script,
A typical usage might be to provide some help text:
#!/usr/bin/env bash
usage() {
cat << END_USAGE
Refresh database tables.
usage: ${0##*/} [-h|--help] [-A|--no-archive]
where: --no-archive flag will _skip_ the archive jobs
END_USAGE
}
# ... parsing command line options here ...
if [[ $flag_help == "true" ]]; then
usage
exit 0
fi
Like here documents, here strings (or "herestrings") provide input to a command.
However, while heredocs are given as a block of text, herestrings are given as a single string of text.
Here strings use the <<< "text"
syntax.
tr 'a-z' 'A-Z' <<< "upper case this string"
Output:
UPPER CASE THIS STRING
Unlike heredocs, no ending delimiter is required.
A pipeline can be used instead of a here string:
echo "upper case this string" | tr 'a-z' 'A-Z'
So why use a here string?
Consider the case where you get the string as output from a long-running computation, and you want to feed the result to two separate commands. Using pipelines, you have to execute the computation twice:
some_long_running_calculation | first_command
some_long_running_calculation | second_command
A more efficient approach is to capture the output of the computation (using command substutition), and use here strings to provide input to the two subsequent commands:
result=$( some_long_running_calculation )
first_command <<< "$result"
second_command <<< "$result"
Here's a real-world application of that example:
# initialize the output CSV file
echo "ID,VALUE" > data.csv
url='https//example.com/api/query?page=1'
while true; do
json=$( curl "$url" )
# convert the results part of the response into CSV
jq -r '.results[] | [.id, .value] | @csv' <<< "$json"
# get the URL for the next page
url=$( jq -r '.next_url // ""' <<< "$json" )
if [[ "$url" == "" ]]; then
break
fi
done >> data.csv
Note the position of the output redirection.
All output from the while loop will be appended to the file data.csv
.
Because these are just forms of redirection, they can be combined with other redirection operations:
cat <<< END_OF_TEXT > output.txt
This is my important text.
END_OF_TEXT
awk '...' <<< "$my_var" >> result.csv
Here documents (or "heredocs") are a flexible and convenient way to manage multi-line input in Bash scripts. They simplify the process of embedding text and data directly within your scripts, making them more self-contained and easier to read.
Here strings (or "herestrings") are like here documents, but offer a simpler, more dynamic syntax.