Indented Here Doc

When shell encounters the syntax <<- delimiter (or <<- 'delimiter'or <<- "delimiter"), it starts creating a multi-line buffer using these rules:

  1. A line beginning with any amount of TAB and then exactly delimiter will end the contents

  2. All leading occurrences of TAB on each line will be removed

The buffer created is sent as stdin to the program of your choice. For example:

1 #!/bin/sh
2 if : true; then
3     cat <<- EOF
4     This is line 1
5         This is line 2
6     EOF
7 fi

Produces:

This is line 1
This is line 2

The leading tabs are stripped from each line.

This may be undesirable and a simple solution exists.

1 #!/bin/sh
2 if : true; then
3     awk 'sub(/^\\\t/,"\t")||1' <<- EOF
4     This is line 1
5     \    This is line 2
6     EOF
7 fi

We changed the invocation of cat on line 3 to awk and a backslash (\) was placed before TAB series we want to retain.

The awk command removes any occurrences of backslash at the start of a line if the following character is TAB.

In awk the syntax is PREDICATE { ACTION }. The { ACTION } part is optional and if not present, defaults to { print } which prints the contents of $0, usually the entire line but this can be modified. The PREDICATE part is either a series of conditions or a keyword such as BEGIN or END. If you have an action without predicate, the action is run for every line. When using one or more conditions as a predicate without an action, if the predicate as-a-whole evaluates to non-zero, the line is printed.

Using sub() in the predicate part of the awk syntax would normally mean the default action of { print } will only execute for the line if the regular expression /^\\\t/ is found. However, because we have placed ||1 after the invocation of sub() in the awk predicate, a zero result from sub() is translated to one. The result is that every line is printed, regardless of whether sub() was able to make a replacement.

You can use the indented here-doc technique with dynamic content too. Take the following text snippet for example, stored in a file named TABTEST:

Line 1
    Line 2
        Line 3

A script can import the contents of this file and embed it in an indented here-doc:

1 #!/bin/sh
2 if : true; then
3     awk 'sub(/^\\\t/,"\t")||1' <<- EOF
4     $( cat TABTEST )
5     \            Line 4
6     \                Line 5
7     EOF
8 fi

Produces:

Line 1
    Line 2
        Line 3
            Line 4
                Line 5

The information from TABTEST does not need to be modified because the stripping of leading tab characters by using >>- happens only on pre-interpolated text. That is to explain that line 4 of the above script, leading tabs are stripped on $( cat TABTEST ) before it is translated into the contents of TABTEST, not after.

Last updated