Bash Scripting 18 ๐ง Here Documents โ Embedding multi-line strings and inline scripts
A here-document is a redirection that feeds a block of text to a command’s standard input. The text is written inline in the script, between a start marker and an end marker, and the shell treats it as if it were a file. The previous chapter covered pipes and process substitution, which connect a command’s streams to another command’s streams; this chapter covers the here-document, which connects a command’s stdin to a literal block of text in the script. The here-document is the mechanism that makes multi-line input readable and maintainable, and it is the standard way to write configuration files, SQL queries, and inline scripts from a shell script.
The syntax is command <<DELIMITER, followed by the content, followed by the delimiter on a line by itself. The delimiter is an arbitrary word, and the content is passed to the command’s stdin. The <<- form strips the leading tabs from the content, which allows the here-document to be indented for readability. The quoted delimiter <<'DELIMITER' disables the variable expansion and the command substitution, which is the correct choice when the content contains $ or backticks that should be literal.
Key point: The here-document is a redirection, not a string. The content is passed to the command’s stdin, and the shell does not parse it as part of the script. The unquoted delimiter expands the variables and the command substitutions in the content, and the quoted delimiter disables them. The <<- form strips the leading tabs but not the spaces, and the delimiter must be on a line by itself with no trailing characters.
Why here-documents require understanding the delimiter
The expansion problem.
An unquoted delimiter means the shell expands the variables, the command substitutions, and the arithmetic expansions in the content before passing it to the command. This is useful when the content is a template that needs the current values, such as a configuration file that contains the hostname or the port. A quoted delimiter means the shell passes the content literally, without any expansion. This is the correct choice when the content is a script that contains $ variables that should be evaluated by the inner shell, or a document that contains literal dollar signs.
The indentation problem.
The <<- form strips the leading tabs from every line of the content, including the delimiter line. This allows the here-document to be indented inside a function or a conditional block without the indentation becoming part of the content. The <<- form does not strip spaces, only tabs, so the content must be indented with tabs, not spaces. The delimiter line must also be indented with a tab, or the shell will not recognize it as the end marker.
The delimiter-collision problem.
The delimiter is an arbitrary word, and the content must not contain a line that is exactly the delimiter. If the content contains the delimiter, the shell ends the here-document at that line, and the rest of the content is parsed as part of the script. The convention is to use a delimiter that is unlikely to appear in the content, such as EOF, END, or a more specific word like CONFIG_EOF. The delimiter is case-sensitive, so EOF and eof are different.
a. The basic here-document
The basic form is command <<DELIMITER, followed by the content, followed by the delimiter on its own line.
#!/usr/bin/env bash
# Pass a multi-line message to cat
cat <<EOF
Hello, world.
This is a multi-line message.
The current user is $USER.
EOF
The cat command receives the three lines as its stdin, and it prints them to the stdout. The unquoted EOF delimiter allows the shell to expand $USER to the current username. The output includes the expanded value.
The here-document can be used with any command that reads from stdin, not just cat.
# Pass a SQL query to mysql
mysql -u root -p mydb <<EOF
SELECT id, name FROM users WHERE active = 1;
UPDATE users SET last_seen = NOW() WHERE id = 1;
EOF
# Pass a message to mail
mail -s "Report" admin@example.com <<EOF
The backup completed at $(date).
The size is $(du -sh /backup | cut -f1).
EOF
The mysql command receives the SQL query as its stdin, and the mail command receives the message body. The unquoted delimiter allows the command substitution $(date) to be expanded before the content reaches the command.
b. The quoted delimiter and the literal content
The quoted delimiter <<'DELIMITER' disables the variable expansion and the command substitution. The content is passed literally, which is the correct choice for scripts and documents that contain $ or backticks.
#!/usr/bin/env bash
# Pass a script to a remote host without expansion
ssh user@host <<'EOF'
cd /var/www
if [ -f config.php ]; then
echo "Config exists"
php artisan migrate --force
fi
EOF
The <<'EOF' delimiter means the $ in the content is not expanded by the local shell. The remote shell receives the content literally, and it expands the $ variables when it runs the script. If the delimiter were unquoted, the local shell would expand the variables before sending the script, which would be the wrong behavior for a script that should run on the remote host.
The quoted delimiter is also the correct choice for a document that contains literal dollar signs, such as a shell script that is written to a file.
# Write a script to a file without expansion
cat <<'SCRIPT' > /usr/local/bin/backup.sh
#!/usr/bin/env bash
BACKUP_DIR="/backup"
DATE=$(date +%F)
tar -czf "$BACKUP_DIR/backup-$DATE.tar.gz" /home/user
SCRIPT
chmod +x /usr/local/bin/backup.sh
The <<'SCRIPT' delimiter means the $BACKUP_DIR, $DATE, and $(date +%F) are written to the file literally. The script is executed later, and the variables are expanded at that time, not when the here-document is written.
c. The indented here-document and the here-string
The <<- form strips the leading tabs from the content, which allows the here-document to be indented inside a function or a conditional block. The here-string <<< is the single-line variant.
#!/usr/bin/env bash
# Indented here-document
if true; then
cat <<-EOF
This content is indented with tabs.
The tabs are stripped by the <<- form.
EOF
fi
The <<-EOF form strips the leading tabs from the content and the delimiter. The content is indented with tabs, not spaces, and the output is not indented. The if block can be indented for readability, and the here-document content is not affected.
The here-string is the single-line form that passes a string to the command’s stdin.
# Pass a string to grep
grep "hello" <<< "hello world"
# Pass a string to a command with expansion
wc -w <<< "one two three"
The <<< "hello world" form passes the string to the command’s stdin, with a trailing newline. The here-string is the concise alternative to the here-document when the content is a single line.
The here-document can be combined with the process substitution and the pipes.
# Use a here-document with a pipe
cat <<EOF | grep "hello"
hello
world
EOF
# Use a here-document with a command that expects a file
diff <(cat <<EOF
line one
line two
EOF
) /tmp/file.txt
The cat <<EOF | grep "hello" pipeline sends the here-document’s content to grep. The diff <(cat <<EOF ... EOF) /tmp/file.txt form uses the process substitution to expose the here-document’s content as a filename, which allows diff to compare it with a file.
Complete Example Session
#!/usr/bin/env bash
# ============================================
# PART 1: THE BASIC HERE-DOCUMENT
# ============================================
cat <<EOF
Hello, $USER
Today is $(date +%F)
EOF
# Output: the expanded content
# ============================================
# PART 2: THE QUOTED DELIMITER
# ============================================
cat <<'EOF'
$USER is not expanded
$(date) is not executed
EOF
# Output: the literal content
# ============================================
# PART 3: WRITE A CONFIGURATION FILE
# ============================================
cat <<EOF > /tmp/app.conf
hostname=$(hostname)
port=8080
user=$USER
EOF
cat /tmp/app.conf
# Output: the expanded configuration
# ============================================
# PART 4: WRITE A SCRIPT WITHOUT EXPANSION
# ============================================
cat <<'SCRIPT' > /tmp/backup.sh
#!/usr/bin/env bash
echo "Backing up on $(date +%F)"
EOF
SCRIPT
cat /tmp/backup.sh
# Output: the literal script
# ============================================
# PART 5: THE INDENTED HERE-DOCUMENT
# ============================================
if true; then
cat <<-EOF
Indented with tabs
Tabs are stripped
EOF
fi
# ============================================
# PART 6: THE HERE-STRING
# ============================================
grep "hello" <<< "hello world"
# Output: hello world
# ============================================
# PART 7: THE HERE-DOCUMENT WITH A PIPE
# ============================================
cat <<EOF | grep "hello"
hello
world
EOF
# Output: hello
# ============================================
# PART 8: THE HERE-DOCUMENT WITH SSH
# ============================================
ssh user@host <<'EOF'
cd /var/www
echo "Deploying on $(hostname)"
EOF
# The remote shell expands $(hostname)
# ============================================
# PART 9: CLEAN UP
# ============================================
rm -f /tmp/app.conf /tmp/backup.sh
The nine parts demonstrate the here-document surface: the basic form, the quoted delimiter, the configuration file, the script without expansion, the indented form, the here-string, the pipe combination, the SSH usage, and the cleanup. The critical distinction is between Parts 1 and 2: the unquoted delimiter expands the variables, and the quoted delimiter passes the content literally.
Quick Reference
Here-Document Forms
| Form | Expansion | Indentation |
|---|---|---|
<<EOF | Variables and commands expanded | None |
<<'EOF' | No expansion | None |
<<-EOF | Variables and commands expanded | Leading tabs stripped |
<<-'EOF' | No expansion | Leading tabs stripped |
<<< "string" | Here-string | Single line |
Common Delimiters
| Delimiter | Use Case |
|---|---|
EOF | General purpose |
END | General purpose |
SCRIPT | Writing a script to a file |
CONFIG | Writing a configuration file |
SQL | SQL queries |
Here-Document Patterns
| Pattern | Purpose |
|---|---|
cat <<EOF | Print the content |
cmd <<EOF > file | Write the content to a file |
cmd <<'EOF' | Pass the content literally |
cmd <<EOF | cmd2 | Pipe the content |
diff <(cat <<EOF ... EOF) file | Compare the content |
Best Practices
โ Do This:
# Use the quoted delimiter for literal content
cat <<'EOF'
$HOME is not expanded
EOF # โ
# Use the unquoted delimiter for templates
cat <<EOF
Hello, $USER
EOF # โ
# Use <<- for indented here-documents
cat <<-EOF
Indented with tabs
EOF # โ
# Use a unique delimiter
cat <<CONFIG_EOF
...
CONFIG_EOF # โ
# Write a script with the quoted delimiter
cat <<'SCRIPT' > script.sh
#!/usr/bin/env bash
echo "$HOME"
SCRIPT # โ
โ Don’t Do This:
# Use the unquoted delimiter for a script that contains $
cat <<EOF > script.sh
echo "$HOME" # Expanded by the local shell # โ
EOF
# Indent with spaces and use <<-
cat <<-EOF
Indented with spaces # Spaces are not stripped # โ
EOF
# Use a delimiter that appears in the content
cat <<END
END # The here-document ends here # โ
END
# Put the delimiter on the same line as the content
cat <<EOF content EOF # Wrong syntax # โ
# Forget the closing delimiter
cat <<EOF
Content # The shell waits forever # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Variables expanded when they should not be | Unquoted delimiter | Use <<'EOF' |
| Content not indented | <<- strips tabs only | Use tabs, not spaces |
| Here-document never ends | Delimiter not on its own line | Put the delimiter on a line by itself |
| Delimiter in the content | The content contains the delimiter | Use a unique delimiter |
<<- does not strip indentation | Content indented with spaces | Indent with tabs |
Real-World Examples
1. Print a message
cat <<EOF
Hello, $USER
EOF
2. Write a configuration file
cat <<EOF > /tmp/app.conf
port=8080
host=$(hostname)
EOF
3. Write a script
cat <<'SCRIPT' > /tmp/script.sh
#!/usr/bin/env bash
echo "$HOME"
SCRIPT
4. SQL query
mysql -u root mydb <<EOF
SELECT * FROM users;
EOF
5. SSH remote command
ssh user@host <<'EOF'
echo "Remote: $(hostname)"
EOF
6. Indented here-document
cat <<-EOF
Indented with tabs
EOF
7. Here-string
grep "hello" <<< "hello world"
8. Here-document with pipe
cat <<EOF | grep "x"
x
y
EOF
9. Here-document with diff
diff <(cat <<EOF
line one
EOF
) /tmp/file.txt
10. Here-document with mail
mail -s "Report" admin@example.com <<EOF
Backup completed at $(date).
EOF
Visual
The Here-Document
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE CONTENT BETWEEN THE DELIMITERS IS THE STDIN โ
โ โ
โ cat <<EOF โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Hello, $USER โ โ
โ โ Today is $(date) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ EOF โ
โ โ โ
โ โผ โ
โ The content is passed to cat's stdin. โ
โ The unquoted EOF expands $USER and $(date). โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Quoted vs Unquoted Delimiter
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE QUOTED DELIMITER DISABLES THE EXPANSION โ
โ โ
โ Unquoted: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ cat <<EOF โ โ
โ โ Hello, $USER โ โ
โ โ EOF โ โ
โ โ โ Hello, alice (expanded) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Quoted: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ cat <<'EOF' โ โ
โ โ Hello, $USER โ โ
โ โ EOF โ โ
โ โ โ Hello, $USER (literal) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The <<- Indented Form
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ <<- STRIPS THE LEADING TABS โ
โ โ
โ if true; then โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ cat <<-EOF โ โ
โ โ Indented with tabs โ โ
โ โ EOF โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ fi โ
โ โ โ
โ โผ โ
โ The output is not indented. โ
โ The content must be indented with tabs, not spaces. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Basic syntax | command <<DELIMITER |
| Expansion | Unquoted delimiter expands; quoted does not |
| Indentation | <<- strips leading tabs |
| Here-string | <<< "string" |
| Delimiter | Arbitrary word, on a line by itself |
| Common delimiters | EOF, END, SCRIPT, CONFIG |
| Use case | Multi-line input, configuration files, scripts |
| POSIX | Supported in POSIX shells |
Key takeaways:
- A here-document feeds a block of text to a command’s stdin. The content is written inline in the script, between the start marker and the end marker. The shell does not parse the content as part of the script; it passes it to the command’s stdin .
- The unquoted delimiter expands the variables and the command substitutions. The
<<EOFform expands$USERand$(date)before passing the content. The<<'EOF'form disables the expansion and passes the content literally . - The
<<-form strips the leading tabs. It allows the here-document to be indented inside a function or a conditional block, and the content must be indented with tabs, not spaces. The delimiter line must also be indented with a tab . - The delimiter must be on a line by itself. The content must not contain a line that is exactly the delimiter, or the shell ends the here-document at that line. The convention is to use a unique delimiter like
EOForCONFIG_EOF. - The here-document is useful for configuration files, SQL queries, and scripts. The
cat <<EOF > fileform writes the content to a file, and thecat <<'SCRIPT' > script.shform writes a script without expanding its variables . - The here-string is the single-line variant. The
<<< "string"form passes the string to the command’s stdin, with a trailing newline. It is the concise alternative to the here-document when the content is a single line.
Remember: A here-document is a redirection that feeds a multi-line block of text to a command’s stdin. The unquoted delimiter expands the variables, and the quoted delimiter passes the content literally. The <<- form strips the leading tabs, which allows the here-document to be indented. The delimiter must be on a line by itself, and the content must not contain a line that is exactly the delimiter. The here-string is the single-line variant. The result is a script that can write configuration files, send SQL queries, and create scripts without the intermediate files or the quoting complexity.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!