'Equivalent of python's textwrap dedent in bash

I have a variable containing a multi-line string in bash:

mystring="foo
          bar
          stack
          overflow"

Obviously this gives a ton of indentation when I echo "$mystring". In python I would simply import textwrap and use dedent on the string, which brings me here. Is there something like python's dedent module thats exists for bash?



Solution 1:[1]

The “here document” feature allows a multi-line string to be defined as input to a command:

$ cat <<_EOT_
    Lorem ipsum dolor sit amet,
        consectetur adipiscing elit.
    Morbi quis rutrum nisi, nec dignissim libero.
_EOT_
    Lorem ipsum dolor sit amet,
        consectetur adipiscing elit.
    Morbi quis rutrum nisi, nec dignissim libero.

The Bash manual section on here documents describes an option to allow indentation in the source, and strip it when the text is is read:

Here Documents

[…]

If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter. This allows here-documents within shell scripts to be indented in a natural fashion.

Which looks like this:

$ cat <<-_EOT_
    Lorem ipsum dolor sit amet,
        consectetur adipiscing elit.
    Morbi quis rutrum nisi, nec dignissim libero.
_EOT_
Lorem ipsum dolor sit amet,
    consectetur adipiscing elit.
Morbi quis rutrum nisi, nec dignissim libero.

The problem with that is it will strip only TAB (U+0009) indentation, not spaces. That is a severe limitation if your coding style forbids TAB characters in source code :-(

Solution 2:[2]

This is in altternative, to run python dedent code in bash script.

python_dedent_script="
import sys, textwrap
sys.stdout.write(textwrap.dedent(sys.stdin.read()))
"
function dedent_print() {
    python -c "$python_dedent_script" <<< "$1"
}
dedent_print "
  fun( abc, 
    def, ghi )
  jkl
"

would output:

fun( abc, 
  def, ghi )
jkl

Solution 3:[3]

Minimal usage demo

text="this is line one
      this is line two
      this is line three\n"
dedent text
printf "$text"

Function and Details

I copied the sed part from @Andreas Louv here.

dedent() {
    local -n reference="$1"
    reference="$(echo "$reference" | sed 's/^[[:space:]]*//')"
}

text="this is line one
      this is line two
      this is line three\n"

echo "BEFORE DEDENT:"
printf "$text"
echo ""

# `text` is passed by reference and gets dedented
dedent text

echo "AFTER DEDENT:"
printf "$text"
echo ""

text gets passed into the dedent function by reference so that modifications to the variable inside the function affect the variable outside the function. It gets dedented inside that function by accessing that variable through its reference variable. When done, you print text as a regular bash string. Since it contains \n format chars, pass it as the format string (first argument) to printf to interpret them.

Output:

BEFORE DEDENT:
this is line one
      this is line two
      this is line three

AFTER DEDENT:
this is line one
this is line two
this is line three

References:

  1. my answer demonstrating the benefits of Python's textwrap.dedent() so you can see why we'd want this feature in bash too: Multi line string with arguments. How to declare?
  2. The sed stuff by @Andreas Louv to remove preceding spaces: Equivalent of python's textwrap dedent in bash

Solution 4:[4]

Instead of indenting the string, suppress the initial newline that causes the need for indentation.

mystring="\
foo
bar
stack
overflow"

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 bignose
Solution 2 hrushikesh
Solution 3
Solution 4 chepner