| |

Bash Scripting 18 ๐Ÿง Here Documents โ€” Embedding multi-line strings and inline scripts

โฑ๏ธ Estimated read: 13 min | |

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

FormExpansionIndentation
<<EOFVariables and commands expandedNone
<<'EOF'No expansionNone
<<-EOFVariables and commands expandedLeading tabs stripped
<<-'EOF'No expansionLeading tabs stripped
<<< "string"Here-stringSingle line

Common Delimiters

DelimiterUse Case
EOFGeneral purpose
ENDGeneral purpose
SCRIPTWriting a script to a file
CONFIGWriting a configuration file
SQLSQL queries

Here-Document Patterns

PatternPurpose
cat <<EOFPrint the content
cmd <<EOF > fileWrite the content to a file
cmd <<'EOF'Pass the content literally
cmd <<EOF | cmd2Pipe the content
diff <(cat <<EOF ... EOF) fileCompare 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

PitfallWhy It HappensFix
Variables expanded when they should not beUnquoted delimiterUse <<'EOF'
Content not indented<<- strips tabs onlyUse tabs, not spaces
Here-document never endsDelimiter not on its own linePut the delimiter on a line by itself
Delimiter in the contentThe content contains the delimiterUse a unique delimiter
<<- does not strip indentationContent indented with spacesIndent 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

ItemValue
Basic syntaxcommand <<DELIMITER
ExpansionUnquoted delimiter expands; quoted does not
Indentation<<- strips leading tabs
Here-string<<< "string"
DelimiterArbitrary word, on a line by itself
Common delimitersEOF, END, SCRIPT, CONFIG
Use caseMulti-line input, configuration files, scripts
POSIXSupported 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 <<EOF form expands $USER and $(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 EOF or CONFIG_EOF .
  • The here-document is useful for configuration files, SQL queries, and scripts. The cat <<EOF > file form writes the content to a file, and the cat <<'SCRIPT' > script.sh form 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.


Was this helpful?


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!