Here Document (heredoc)[1] is a method for inputting <span>multi-line strings</span> in a <span>shell</span>. It allows users to directly input multiple lines of text in the command line or script without using multiple echo commands or escape characters.
<span>Basic syntax:</span>
[n]<<[−]word
here-document-content
delimiter
<span><<</span>and<span>word</span>can have spaces in between<span>n</span>is a number representing the file descriptor<span>delimiter</span>is the<span>delimiter</span>and must be on a line by itself- If the redirection operator is
<span><<</span>, then it cannot contain<span>whitespace</span>before or after - If the redirection operator is
<span><<−</span>, it can only contain<span>leading tabs</span> - Same as
<span>word</span>
This redirection method instructs the <span>shell</span> to read input from the current source until it encounters a line that contains only the <span>delimiter</span> (with no trailing spaces). All lines read will be treated as the <span>standard input</span> for the command (or file descriptor <span>n</span> if specified).
1. Specifying File Descriptor
# heredoc.sh
# Using file descriptor
exec 3<< EOF
This is the content of file descriptor 3
EOF
cat <&3
# Close
exec 3<&-
<span>Output:</span>
bash-5.3$ bash heredoc.sh
This is the content of file descriptor 3
2. Unquoted Delimiter
If <span>word</span> is not quoted with single or double quotes, the delimiter is <span>word</span> itself, and the text of the <span>Here Document</span> will be treated similarly to a <span>double-quoted string</span>: all lines will undergo <span>parameter expansion</span>, <span>command substitution</span>, and <span>arithmetic expansion</span>, the character sequence <span>\n</span> will be treated literally, and <span>\</span>, <span>$</span>, and ` (backtick) characters must be escaped with <span>\</span>, and double quote characters have no special meaning.
The following example uses <span>EOF</span> as the <span>word</span> delimiter.
# heredoc.sh
name="Zhang San"
age=25
price=5000
cat << EOF
Name: $name
Age: $(( age + 1 ))
Salary: \$(( $price * 2 ))
Occupation: Programmer
\n
Backslash: \
Backtick: `
Command substitution: `echo "hello"`
Command substitution: $(echo "world")
Description: "Double quote characters have no special meaning"
EOF
<span>Output:</span>
bash-5.3$ bash heredoc.sh
Name: Zhang San
Age: 26
Salary: $10000
Occupation: Programmer
\n
Backslash: \
Backtick: `
Command substitution: hello
Command substitution: world
Description: "Double quote characters have no special meaning"
3. Quoted Delimiter
If any part of <span>word</span> is quoted with single or double quotes, the delimiter is the result of removing the quotes from <span>word</span>, and the lines in the <span>Here Document</span> will not be expanded.
<span>1. Single Quotes</span>
# heredoc.sh
name="Zhang San"
cat << 'EOF'
Variable: $name will not expand
Command: `date` will not execute
Backslash: \ does not need to be escaped
Special characters: $ ` \
EOF
<span>Output:</span>
bash-5.3$ bash heredoc.sh
Variable: $name will not expand
Command: `date` will not execute
Backslash: \ does not need to be escaped
Special characters: $ ` \
<span>2. Double Quotes</span>
# heredoc.sh
name="Zhang San"
cat << "EOF"
Variable: $name will not expand
Command: `date` will not execute
Backslash: \ does not need to be escaped
Special characters: $ ` \
EOF
<span>Output:</span>
bash-5.3$ bash heredoc.sh
Variable: $name will not expand
Command: `date` will not execute
Backslash: \ does not need to be escaped
Special characters: $ ` \
This shows that the effect of single or double quotes on the <span>Here Document</span> is the same.
<span>3. Partial Quoting</span>
Using <span>E&O"F</span> as the <span>word</span> delimiter. The quotes will be removed to get <span>EOF</span>.
# heredoc.sh
name="Zhang San"
cat << E"O"F
Variable: $name will not expand
Command: `date` will not execute
Backslash: \ does not need to be escaped
Special characters: $ ` \
EOF
<span>Output:</span>
bash-5.3$ bash heredoc.sh
Variable: $name will not expand
Command: `date` will not execute
Backslash: \ does not need to be escaped
Special characters: $ ` \
So the meaning of <span>quote removal</span> is as follows:
- Original: << ‘EOF’ → Delimiter: EOF
- Original: << “EOF” → Delimiter: EOF
- Original: << E’O’F → Delimiter: EOF
- Original: << “E”OF → Delimiter: EOF
- Original: << EO”F” → Delimiter: EOF
4. Line Continuation with Unquoted Delimiter
# heredoc.sh
cat << EOF
First line \
Second line
Third line
EOF
<span>Backslash</span>–<span>newline</span> is removed, merging the two lines into one:
bash-5.3$ bash heredoc.sh
First line Second line
Third line
5. Removing Leading Tabs
If the redirection operator is <span><<-</span>, the shell will remove <span>leading tabs</span> from the input lines and the line containing the delimiter. This allows the <span>Here Document</span> in shell scripts to be indented naturally.
# heredoc.sh
# Using <<<- to remove leading tabs
function remove_tab() {
cat <<- END
First line (with tab)
Second line (with indentation)
Third line
END
}
remove_tab
<span>Output:</span>
bash-5.3$ bash heredoc.sh
First line (with tab)
Second line (with indentation)
Third line
6. Using Variables as Delimiters is Not Allowed
The shell does not perform parameter and variable expansion, command substitution, arithmetic expansion, or filename expansion on <span>word</span>.
# heredoc.sh
age=28
var_delim="END_MARKER"
cat << $var_delim
age: $age
END_MARKER
<span>Syntax check and execution:</span>
bash-5.3$ bash -n heredoc.sh
heredoc.sh: line 8: warning: here-document delimited by end-of-file (wanted "$var_delim")
bash-5.3$ bash heredoc.sh
heredoc.sh: line 8: warning: here-document delimited by end-of-file (wanted "$var_delim")
age: 28
END_MARKER
bash-5.3$ bash -e heredoc.sh
heredoc.sh: line 8: warning: here-document delimited by end-of-file (wanted "$var_delim")
age: 28
END_MARKER
Modify the script slightly:
# heredoc.sh
age=28
var_delim="END_MARKER"
cat << $var_delim
age: $age
$var_delim
<span>No issues:</span>
bash-5.3$ bash heredoc.sh
age: 28
In fact, <span>$var_delim</span> is used here as a string <span>as a whole</span> as the delimiter.
7. Here String
<span>Here String</span> is a variant of <span>Here Document</span>. It is a convenient redirection feature provided by Bash that allows you to directly pass a <span>string</span> as standard input to a command (or file descriptor <span>n</span> if specified).
[n]<<< word
- n: is a number representing the file descriptor
Here, <span>word</span> will undergo <span>tilde expansion</span>, <span>parameter and variable expansion</span>, <span>command substitution</span>, <span>arithmetic expansion</span>, and <span>quote removal</span>. However, filename expansion and word splitting will not be performed. The final result will be passed to the command as a single string (with a newline character appended at the end).
command <<< "string"
<span>Equivalent to:</span>
echo "string" | command
However, <span>Here String</span> is usually more efficient because it does not require creating a new process (like echo).
# Convert case
$ tr 'a-z''A-Z' <<< "hello world"
HELLO WORLD
# Count the number of words in the string
$ wc -w <<< "This is a test string"
5
# Read data from Here String into variables
$ read first_name last_name <<< "John Doe"
$ echo "First name: $first_name"
First name: John
$ echo "Last name: $last_name"
Last name: Doe
<span>Parameter and Variable Expansion</span>
$ name="Zhang San"
$ greeting="Hello, $name"
$ cat <<< "$greeting"
Hello, Zhang San
<span>Command Substitution</span>
$ cat <<< "Today is $(date)"
Today is 2025年11月26日 星期三 17时05分23秒 CST
<span>Will Not Perform Filename Expansion</span>
bash-5.3$ ls
log1.txt Log10.txt log2.txt log3.txt log4.txt
# echo performed filename expansion
bash-5.3$ echo *.txt | cat
log1.txt Log10.txt log2.txt log3.txt log4.txt
# Using Here String (will not perform filename expansion)
bash-5.3$ cat <<< *.txt
*.txt
bash-5.3$ cat <<< "*.txt"
*.txt
bash-5.3$ set -x
# ??? The author has a question
bash-5.3$ cat <<< ls *.txt
+ cat log1.txt Log10.txt log2.txt log3.txt log4.txt
this is log1.txt
this is log2.txt
# Filename expansion in command substitution
bash-5.3$ cat <<< $(ls *.txt)
+ cat
++ ls log1.txt Log10.txt log2.txt log3.txt log4.txt
log1.txt
Log10.txt
log2.txt
log3.txt
log4.txt
# Normal string
bash-5.3$ cat <<< "ls *.txt"
+ cat
ls *.txt
<span>Multi-line String</span>
# Using $'...' quoting mechanism to interpret backslash escapes
$ cat <<< $'Line 1\nLine 2\nLine 3'
Line 1
Line 2
Line 3
<span>$'string'</span> usage can refer to ANSI_002dC-Quoting[2].
<span>While Loop</span>
while read line; do
echo "Processing: $line"
done <<< "apple
banana
cherry"
<span>Trailing Newline</span>: <span>Here String</span> automatically adds a newline character at the end of the string. If you do not want this newline, you need to handle it carefully.
# Automatically added newline
bash-5.3$ cat <<< "hello" | od -c
0000000 h e l l o \n
0000006
# Remove newline
printf "%s" "$(cat <<< 'hello')"
References
[1]
Here Document (heredoc): https://www.gnu.org/software/bash/manual/html_node/Redirections.html#Here-Documents
[2]
ANSI_002dC-Quoting: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html