Bash supports multiline strings using heredoc syntax. They are extremely useful if you want to embed other programming language code in a Bash script.

For example:

cat << EOF
import os
print(os.getcwd())
EOF

Will output

import os
print(os.getcwd())

This is working and cool, but has one little limitation.

# test.sh
python_code () {
    cat << EOF
    import os
    print(os.getcwd())
    EOF
}
python_code

Doesn’t work.

$ bash test.sh
test.sh: line 9: warning: here-document at line 4 delimited by end-of-file (wanted `EOF')
test.sh: line 10: syntax error: unexpected end of file

You can’t indent code. You need to do this instead:

# test.sh
python_code () {
cat << EOF
import os
print(os.getcwd())
EOF
}
python_code

This works, but looks ugly. It is a known problem - indentation is not supported by heredoc.

The simplest solution is just not to use indentation and be done with the problem, after all, we developers have so many problems to fix, and the deadlines are just around the corner. Nevertheless for some of us the readability is worth fighting for. So let’s fight!

But hola, amigo - not so fast. Heredoc supports indentation and you are lying! See?

# test.sh
python_code () {
    cat <<- EOF
    import os
    print(os.getcwd())
    EOF
}
python_code

Well, yes. There is <<- operator which enables the indentation, but it has one limitation - You must use tabs to indent. Doesn’t work with spaces. So lame.

So unless you indent your bash scripts with tabs - you are out of luck. (you can always commit the sin of mixed indentation, but you didn’t hear that from me)

Now those who believe that tabs > spaces can stop reading, go back to work and start solving real domain problems.

We poor “space” souls must stay and fix this indentation issue.

Fixing the situation a bit

Personally i like to visually simulate the indentation.

# test.sh
python_code () {
    cat << "    EOF"
    import os
    print("Hello world")
    EOF
}
python_code

Looks good enough. But here we are introducing one problem. Look:

$ bash test.sh
    import os
    print("Hello world")

Now we output unecessary indentation and here for example this python code won’t work.

python3 <(python_code)
  File "/dev/fd/63", line 1
    import os
    ^
IndentationError: unexpected indent

Oh no! What to do? Let us increase the level of insanity and “deindent” this code with sed.

python_code () {
    sed -r 's/^.{4}//' << "    EOF"
    import os
    print("Hello world")
    EOF
}

Voila! We even add an extra level if we want to:

python_code () {
    sed -r 's/^.{8}//' << "    EOF"
        import os
        print("Hello world")
    EOF
}

Also let’s make this sed a little bit less scary.

deindent () {
    sed -r 's/^.{8}//'
}

python_code () {
    deindent << "    EOF"
        import os
        print("Hello world")
    EOF
}

That was hacky. Is there a better way? Let me know.