Underline with stars from first non-space character

I am using this bash function to print a phrase with stars below it.

outline ()
{
 titl="$1"
 n="${#titl}"
 st=$( printf '%*s' $n " " | tr ' ' '*' )
 printf '%s\n' "$titl" "$st"
}

The result of outline "Stack Overflow" will be

1234567890123456
Stack Overflow
**************

But with three spaces before the first letter S, the command outline " Stack Overflow" gives

12345678901234567890 
   Stack Overflow
*****************

I want to start the stars from the beginning of the first non-space character, so that the result of outline " Stack Overflow" will be

12345678901234567890 
   Stack Overflow
   **************

Have including column numbers on top to understand where things are to be placed.

4 answers

  • answered 2021-11-29 04:37 scavenger

    this works for all non-space characters: would your teacher accept it?

    outline ()
    {
     titl="$1"
     printf '%s\n%s\n' "${titl}" "${titl//[! ]/*}"
    }
    
    outline "  Stack Overflow"
      Stack Overflow
      ***** ********
    

  • answered 2021-11-29 06:03 Renaud Pacalet

    With only bash the following does what you show, thanks to the extended pattern matching (extglob) option:

    outline () {
      restore="$(shopt -p extglob)"
      shopt -s extglob
      spc="${1/[^[:space:]]*}"
      str="${1##+([[:space:]])}"
      printf '%s\n%s\n' "$1" "$spc${str//?/*}"
      eval "$restore"
    }
    

    Explanation: first record the current extglob status such that it can be restored before leaving the function. Then store the leading spaces in variable spc and the rest in variable str. Finally, print the original string, followed by a line formed by $spc and $str where each character has been replaced by a star.

    A one-liner with sed (tested with GNU sed):

    outline () {
      sed -E 'p;s/^([[:space:]]*)./\1*/;:a;s/\*[^*]/**/;ta' <<< "$1"
    }
    

    Explanation: first print the string. Next, substitute the first non-space character by a star. Then substitute a star followed by a non-star character by two stars and repeat as long as there is a match. Finally sed automatically prints the result.

  • answered 2021-11-29 06:14 Cyrus

    With bash:

    outline ()
    { 
      local titl spaces
      titl="$1"
      
      # extract leading spaces
      [[ $titl =~ ^(\ )* ]]; spaces="${BASH_REMATCH[0]}"
      
      # remove leading spaces from titl
      titl="${titl/#$spaces/}"
      
      echo "$spaces$titl"
      echo "$spaces${titl//?/*}"
    }
    

  • answered 2021-11-29 11:25 M. Nejat Aydin

    In pure bash:

    outline () {
        local leading_blanks=${1%%[![:blank:]]*}
        local trimmed=${1#"$leading_blanks"}
        printf '%s\n%s%s\n' "$1" "$leading_blanks" "${trimmed//?/*}"
    }
    
    outline "   Stack Overflow"
    

    Explanation:

    • ${1%%[![:blank:]]*} deletes from the first non-blank character through end of string, from $1. Remaining characters are just leading blanks, if any.
    • ${1#"$leading_blanks"} removes the leading blank characters (if any) from $1.
    • "${trimmed//?/*}" replaces each character in the variable trimmed with a *.

    For detailed information about all types of parameter expansion in bash, read Shell Parameter Expansion

    Alternatively, a single sed command will do the job, though, I would definitely prefer the pure bash version:

    outline () {
    sed 'h
         s/^[[:blank:]]*//
         s/./*/g
         H
         g
         s/^\([[:blank:]]*\)\(.*\n\)\(.*\)/\1\2\1\3/' <<< "$1"
    }
    

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum