2

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?

Girrafish
  • 2,320
  • 1
  • 19
  • 32

5 Answers5

3

You can use sed to remove leading spaces from each line:

$ sed 's/^[[:space:]]*//' <<< "$mystring"
foo
bar
stack
overflow

Alternative you can (ab)use the fact that read will remove leading and trailing spaces:

$ while read -r line; do printf "%s\n" "$line"; done <<< "$mystring"
foo
bar
stack
overflow

And in your example where you basically want to remove all spaces:

$ echo "${mystring// }"
foo
bar
stack
overflow
Andreas Louv
  • 46,145
  • 13
  • 104
  • 123
  • Let's [put this in a function.](https://stackoverflow.com/a/71169678/4561887) – Gabriel Staples Feb 18 '22 at 07:32
  • If this question is really going to mention "python's textwrap dedent" in the title (which it does as of this writing), then the accepted answer should preserve indentation like Python does, which this answer does not do. As of this writing, only (this answer)[https://stackoverflow.com/a/63228433/724752 ] does. ((This answer)[https://stackoverflow.com/a/38535244/724752 ] does too, but as mentioned there, only for leading tabs, not for leading spaces.) – David Winiecki Apr 22 '22 at 20:28
3

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 :-(

bignose
  • 30,281
  • 14
  • 77
  • 110
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
hrushikesh
  • 177
  • 1
  • 8
  • I know it's not directly related, but why use `write` and `read` instead of `print` and `input`? (I think there is a reason and I want to know.) – David Winiecki Apr 22 '22 at 20:22
  • 1
    @DavidWiniecki, Appreciate your curiosity. With `print` I would have to write if-else blocks with print keyword and print() function so that the code works on both python 2 and 3. Using read/write methods increased its compatibility. – hrushikesh Apr 23 '22 at 22:56
0

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
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
-2

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

mystring="\
foo
bar
stack
overflow"
chepner
  • 497,756
  • 71
  • 530
  • 681
  • This doesn't meet the stated requirements: we *want* the indentation in the source. The question is, *given* the text is indented in the source, how to remove it when it's used by the program. – bignose Jul 23 '16 at 01:31
  • Why include the indentation in the first place if you are just going to strip it? True, I'm assuming that we have control over the assignment, but in cases like this, someone defined `mystring` like this only because they didn't realize they could put the beginning of the string on a separate line, and the indentation is only there to make the assignment "pretty". – chepner Jul 23 '16 at 13:27
  • The indentation is in the source code because continuation lines deserve an extra level of indentation. The indentation is only stripped when it is consumed at run time; it remains in the source for the sake of the humans who read it. – bignose Jul 23 '16 at 13:34
  • That's my point. You only "need" the indentation if you start the value immediately after the quotation mark, which *isn't* necessary. – chepner Jul 23 '16 at 13:37
  • @bignose The reason I need to indent the string is due to PEP8 python styling. The code needs to have a certain structure and properly indented strings is part of them. – Girrafish Jul 23 '16 at 20:40