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?

5 Answers 5

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

1
  • 1
    Though, I tried with bash in ubuntu 18.04 and those leading tab still there. Sorry.
    – Nam G VU
    Commented Dec 28, 2020 at 4:24
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
2
  • 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.) Commented Apr 22, 2022 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
    Commented Apr 23, 2022 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
-2

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

mystring="\
foo
bar
stack
overflow"
5
  • 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
    Commented Jul 23, 2016 at 1: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
    Commented Jul 23, 2016 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
    Commented Jul 23, 2016 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
    Commented Jul 23, 2016 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
    Commented Jul 23, 2016 at 20:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.