Score:0

print multi-line text with spaces aligned on the first line

bt flag

Issue:

I write relatively complex functions where I have to make up the code with spaces.

It is expected that the first line with the content (non-space character) will be determined, N spaces will be cut from it UP to the first non-space character.

Then, in all subsequent lines, the previously determined N-th number of spaces will also be cut out.

Expectation examples:

Let's say there is a certain print function.

In all subsequent cases, my expectation is:

this is the first line,
    this is the second line
  this is the third line.

Example of the cases used:

print '
    this is line one
        this is line two
      this is line three
    '

print '
            this is line one
                this is line two
              this is line three
            '
tmp() {
    tmp2() {
        print '
            this is line one
                this is line two
              this is line three
            '
    }
    tmp2
}
tmp

UPD: based on the practice of analyzing the answers to the issue, I added another one test:

tmp() {
    tmp2() {
        print '
            this
                this is line two
              this is line three
            '
    }
    tmp2
}
tmp

P.S.: My developments (works incorrectly)

print() {
    INPUT_STRING=$1
    
    # https://stackoverflow.com/questions/41010371/bash-counting-letters-in-string-output-always-a-little-different
    COUNT_SPACES_BEFORE_TEXT=$(printf '%s' "${INPUT_STRING}" | grep -o '[^\n]*' | wc -l)
    
    # https://stackoverflow.com/questions/5349718/how-can-i-repeat-a-character-in-bash
    STRING_SPACES=$(printf "%${COUNT_SPACES_BEFORE_TEXT}s" |tr " " " ")
    
    # https://stackoverflow.com/questions/23929235/multi-line-string-with-extra-space-preserved-indentation
    EXPR_SED='1d;$d;'
    EXPR_SED+="s/^${STRING_SPACES}//g"
    printf '%s' "${INPUT_STRING}" | sed "${EXPR_SED}"
}

Please tell me how to do it?

P.S.S.: please note that the input and output are strictly showed in the question.

  1. It is not necessary to offer "similar" solutions like:

    printf '%s\n' \
        "this is line one" \
        "this is line two" \
        "this is line three"
    
  2. No need to offer an analysis of my programming style, etc.

The entrance and exit are clearly indicated. I am going to ignore the answers/comments not on the subject

muru avatar
us flag
In all these cases, you're removing spaces based on the second line while claiming to want to do so based on the first line.
Avraam avatar
bt flag
@muru - you're right, I corrected the query: "first line with the content"
Score:1
jp flag

I am not a fan of using a function's positional arguments to pass a multiline string ... But if you always quote that string, then you can achieve what you want in pure(i.e. using only shell builtins and no external commands used) bash with a function like this:

my_print () { 
count=1
# Loop over multiline positional argument/s "$*" one line at a time
while IFS= read -r l; do
# Find the number of leading space characters in the first ...
# none empty/blank(all spaces) line and store it in "$spnum"
  if [ "$count" -eq "1" ] && [ ! -z "${l// /}" ]; then
    l2="${l//[^ ]*/}"
    spnum="${#l2}"
    count=2
    fi
# Print each none empty/blank(all spaces) line ...
# after removing "$spnum" characters from its beginning
  [ "$count" -eq "2" ] && [ ! -z "${l// /}" ] &&  echo "${l:spnum}"             
  done <<<"$*"
}

Usage demonstration:

$ my_print '
    this is line one
        this is line two
      this is line three
    '
this is line one
    this is line two
  this is line three
$
$ my_print '
            this is line one
                this is line two
              this is line three
            '
this is line one
    this is line two
  this is line three
$
$ tmp() {
    tmp2() {
        my_print '
            this is line one
                this is line two
              this is line three
            '
    }
    tmp2
}
tmp
this is line one
    this is line two
  this is line three
$
$ tmp() {
    tmp2() {
        my_print '
            this
                this is line two
              this is line three
            '
    }
    tmp2
}
tmp
this
    this is line two
  this is line three
Avraam avatar
bt flag
Wow! Looks like what I need, thanks a lot!
Score:1
cn flag

This is an implementation with awk, assuming tabs are not whitespace.

print(){
  printf "%s" "$@" |
  awk 'BEGIN{ len = -1 }
  NF>0 { if(len==-1){
              len = index($0,$1)-1; 
              prefix = substr($0,1,len) 
         }
       }
  /./ { start = substr($0,1,len)
        if(start==prefix){
           if(length($0)>len)print substr($0,len+1)
        } else print
  }'
}

The string is piped into awk and each line tested. len is set to -1.NF>0 is true if the line is not all spaces. $1 holds the first word. The index of that word in the line $0, minus 1, is the length of the prefix string of whitespace.

/./ matches non-empty lines. The first len characters are put in start. If this is the same as the prefix, the line is printed, starting from position len+1, unless the line is exactly just the whitespace prefix (such as at the end of the input; it is your choice). Else the line, which doesn't have the right whitespace is printed, (your choice).

Avraam avatar
bt flag
The proposed option looks interesting, thank you! But I found a bug... (unfortunately, I can't pass the image/code in the comments). To reproduce it, it is enough to simply delete all other words from the first line, leaving only "this" - then the space-layout will float.
Avraam avatar
bt flag
added the fourth test to the issue, marked as UPD
meuh avatar
cn flag
Oops, should be `NF>0` not `NF>1`. Sorry.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.