Serious Shell Programming
  • Introduction
  • Acknowledgements
  • Basics
    • Strings
      • Single-Quotes
      • Double-Quotes
      • Unquoted Strings
      • Compound Strings
    • Here Documents
      • Here Doc
      • Indented Here Doc
      • Literal Here Doc
      • In-Memory Here Doc
    • Conditionals
      • Built-in test
      • Parameter Conditionals
      • Parameter test
    • Regex
      • grep
      • awk
      • pcre
    • Control Flow
      • Binary Operators
      • if-elif-else
      • case Statement
      • for Loop
      • while Loop
      • Functions
  • shellcheck
    • Introduction
    • Bad Advice
  • Style
    • awk
    • case
    • Redirection
    • Comments
    • trap
  • String Functions
    • substr
    • sprintf
    • replace
    • replaceall
    • replacestart
    • replaceend
    • fnmatch
  • awk
    • Pre-declaring Arrays
    • Sorting Arrays
  • Know Your limits
    • Arguments
    • Environment Variables
    • Solutions
Powered by GitBook
On this page

Was this helpful?

  1. Basics
  2. Here Documents

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.

PreviousHere DocNextLiteral Here Doc

Last updated 5 years ago

Was this helpful?