LearnLinux FoundationsHeredocs

The <<- Tab-Stripped Heredoc

cat <<- EOF - a hands-on Linux lab on a real virtual machine.

One dash turns << into <<-, the heredoc that strips leading tabs from its body and closing delimiter so you can indent blocks inside scripts. Tabs only, never spaces; expansion unchanged. Plus one-line stdin checks with the here-string, grep -c, and wc -w.

Here is a small mystery from real scripts. You know the here-document from the heredoc lesson: cat << EOF feeds every line that follows to cat, until a line that is exactly the delimiter word EOF. And you know its iron rule: the body is fed EXACTLY as typed.

That exactness has a sting. Indent a body line with a tab, and the tab rides along into the output. Watch plain << carry one indented line straight through.

cat << EOF
	this had a leading tab
EOF

The output arrives still pushed in by that tab:

	this had a leading tab

Now the mystery. Add ONE character to the operator, and the exact same tab-indented body comes out clean at the left margin. One keystroke, and the shell starts eating your indentation on purpose. This lesson is about that character: where it saves you, and the one rule about it that trips almost everyone.

The black boxes below are a practice terminal: a safe sandbox that only checks the one command each step teaches, so you cannot break anything. A heredoc spans several lines, and the practice box takes a single line, so the multi-line examples in this lesson are read-only: read them, picture typing them. The one-line commands you DO type are graded for real, and you run the multi-line forms on a real machine in the module capstone.

The character is a dash. Write <<- instead of << and the heredoc gains exactly one new behavior: the shell strips leading tab characters from every body line, and from the closing delimiter line too.

Everything else works as you learned in the heredoc lesson. The delimiter word is still yours to pick. The body still ends at a line holding just that word. The text still pours into the command's stdin (standard input, the channel a command reads from when you hand it no file).

Why would anyone want their input edited on the way in? Indentation. Scripts are indented: commands inside a function sit one stop in, commands inside a condition sit deeper. A plain << forces its body against the left margin, breaking the visual shape of your script. <<- lets the block sit indented with the surrounding code while the tabs quietly vanish from what the command receives.

The dash form is not new cleverness. It shipped with the original Bourne shell in the late 1970s and is still specified by POSIX today, so it works in every shell you will meet.

Time to see it. In the block below, each body line starts with one real tab, and so does the closing EOF. Before you read the output, decide: will the two lines print with their tabs, or flush against the left margin?

cat <<- EOF
	tab-indented one
	tab-indented two
	EOF
tab-indented one
tab-indented two

Flush left. Both tabs are gone: cat never saw them, because the shell removed each leading tab before feeding the line to stdin.

Now look back at the closing line, because that is the quiet half of the trick. The final EOF was tab-indented too, and the shell still recognized it as the delimiter. Under plain << that indented line would NOT close the heredoc, since the delimiter must sit alone at the very start of its line. <<- strips the delimiter line's tabs before comparing, so the closing word can sit at the same depth as the rest of your code.

Here is the rule that trips almost everyone: <<- strips tab characters ONLY. Spaces are never touched, no matter where they sit.

The body line below starts with one tab, then has four spaces after the colon. Before you read the output, decide: which survives, the tab or the spaces?

cat <<- EOF
	kept:    four-spaces-after-colon
	EOF
kept:    four-spaces-after-colon

The leading tab is gone; the four spaces are intact. <<- shaves tabs off the FRONT of each line and stops the moment it meets anything else. It is a tool for lining up code, not for trimming arbitrary whitespace out of your text.

Many editors insert spaces when you press the Tab key. A <<- block indented with spaces will NOT flatten, and the indentation stays in the output. When a <<- heredoc refuses to de-indent, check first whether the indentation is real tab characters.

The real home of <<- is inside indented code. Below, a heredoc lives inside a shell function (a named block of commands; functions get their own lesson later). The body and the closing delimiter are tab-indented to match the code around them. Before you read the output, decide: does the indented EOF still end the block?

run() {
cat <<- EOF
	indented body
	EOF
}
run
indented body

It does. The source reads cleanly on the screen, and the output is clean at the margin. That pairing, pretty source and clean input, is the entire reason the dash form exists.

Milestone reached: you now know the one character that separates a heredoc that leaks its indentation from one that does not, and you know its one blind spot, spaces.

The dash changes tab handling and nothing else. Everything else you learned about heredocs holds under <<-. A quoted delimiter (<<- 'EOF') still turns off expansion. And with the delimiter unquoted, the shell still expands $variables in the body before the command sees the text.

Prove that expansion half with your own fingers. The practice box takes one line, so use the family's one-line feeder <<<, the here-string. It feeds one string to stdin, and it gets its own full lesson next; all you need now: everything after <<< on the line becomes the input. Set a variable, then feed it inside double quotes, both on one line separated by a semicolon (the shell runs the first command, then the second).

Before you run this, decide: will cat print the literal characters $greeting, or the text stored in the variable?

greeting='hello from a variable'; cat <<< "$greeting"

prompt: student@linuxcamp:~$ answer: greeting='hello from a variable'; cat <<< "$greeting" output: hello from a variable hint: Two commands on one line: greeting='hello from a variable' then a semicolon, then cat <<< "$greeting" with double quotes around the variable.

The stored text came out, not the dollar sign. Inside double quotes the shell expanded $greeting to hello from a variable before cat read a single character. An unquoted-delimiter heredoc body behaves the same way, dash or no dash. The dash never touches expansion; it only eats leading tabs.

When an operator edits your input on the way in, the engineer's habit is to verify what actually arrived. A quick check for text you just fed to stdin is grep, the pattern finder, which gets its full lesson later in the text processing module. All you need now: grep prints the lines containing a pattern, and it also has a counting flag, -c, which counts matching lines instead of printing them.

Feed the one-line string red green blue and count the lines containing green. Before you run it, decide: the input is a single line, so what is the biggest number this can possibly print?

grep -c green <<< 'red green blue'

prompt: student@linuxcamp:~$ answer: grep -c green <<< 'red green blue' output: 1 hint: grep with the -c flag, the pattern green, then the one-line feeder and the quoted string: grep -c green <<< 'red green blue'

1: exactly one line of the input contains green, and the input was one line, so the count can never go higher here. The count is of LINES that match, not words. Drop the -c and grep prints the matching lines themselves instead of counting them. Keep both forms in reach, because the training wheels come off next.

Three tasks, no command spelled out. Each uses only what you have already typed or been reminded of: the one-line feeder, grep with and without its counting flag, and wc -w, the word counter cousin of the wc you met back in the wc lesson. Read the goal, recall the shape, type it.

Challenge 1. Using the one-line feeder, count how many lines of the string red green blue contain the word purple.

prompt: student@linuxcamp:~$ answer: grep -c purple <<< 'red green blue' output: 0 hint: Same counting command you just ran, different search word. A word that is absent still gets an honest count.

Challenge 2. Feed the same string red green blue and make grep print the whole matching line when it contains green.

prompt: student@linuxcamp:~$ answer: grep green <<< 'red green blue' output: red green blue hint: This time you want the line itself, not a count. Which flag do you leave off?

Challenge 3. Feed the string one two three four to the word counter and get the number of words.

prompt: student@linuxcamp:~$ answer: wc -w <<< 'one two three four' output: 4 hint: wc counts things, and one of its flags picks words. Feed the string with the same operator you have used all lesson.

You earned this card. It is the whole dash form in six questions:

One character, one job: <<- is << for indented code. It strips leading tabs from the body and the closing delimiter, so your script can look right and your input can be clean at the same time.

At an interactive prompt, plain << is usually all you need, since you rarely indent there. The dash form pays off inside scripts. When you write one, make sure the indentation is real tabs, and the block will flatten exactly as you saw here.

You have seen plain << leak a tab, watched <<- strip tabs from body and delimiter alike, proved that spaces never qualify, and confirmed the dash leaves variable expansion alone. You also kept your stdin habits sharp: feeding a variable, counting matches, counting words.

The module ends with one real Linux machine: the capstone mission. A bash VM boots just for you, tiny fixture files already in place, and the objectives name goals, never commands. Two of those objectives are tab-stripping jobs: an indented heredoc that must come out clean, and a proof that the dash keeps spaces. You will know the character to reach for.

Finish the other lessons in this module, then go strip some tabs for real.

Practice The <<- Tab-Stripped Heredoc in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.