12

I have created this function, which prints the output as seen in the example image. But, the implementation of this function seems too complex.

Is there a way that I can improve it or implement an alternative solution?

This is an example of the output after executing the "box_out" function with the string argument 'Love Unix & Linux'

#!/bin/bash
function box_out() {
    input_char=$(echo "$@" | wc -c)
    line=$(for i in `seq 0 $input_char`; do printf "-"; done)
    # tput This should be the best option. what tput does is it will
    # read the terminal info and render the correctly escaped ANSI code
    # for you.
    # Code like \033[31m will break the readline library in some of the
    # terminals.
    tput bold
    line="$(tput setaf 3)${line}"
    space=${line//-/ }
    echo " ${line}"
    printf '|' ; echo -n "$space" ; printf "%s\n" '|';
    printf '| ' ;tput setaf 4; echo -n "$@"; tput setaf 3 ; printf "%s\n" ' |';
    printf '|' ; echo -n "$space" ; printf "%s\n" '|';
    echo " ${line}"
    tput sgr 0
}

box_out $@

damon
  • 103
Rahul Patil
  • 24,711

5 Answers5

22

As your shebang and syntax indicates unportable bash, I prefer it this way:

function box_out()
{
  local s="$*"
  tput setaf 3
  echo " -${s//?/-}-
| ${s//?/ } |
| $(tput setaf 4)$s$(tput setaf 3) |
| ${s//?/ } |
 -${s//?/-}-"
  tput sgr 0
}

Of course, you can optimize it if you wish.

Update as requested in comment, to handle multiline text too.

function box_out()
{
  local s=("$@") b w
  for l in "${s[@]}"; do
    ((w<${#l})) && { b="$l"; w="${#l}"; }
  done
  tput setaf 3
  echo " -${b//?/-}-
| ${b//?/ } |"
  for l in "${s[@]}"; do
    printf '| %s%*s%s |\n' "$(tput setaf 4)" "-$w" "$l" "$(tput setaf 3)"
  done
  echo "| ${b//?/ } |
 -${b//?/-}-"
  tput sgr 0
}

Call it with multiple parameters, like box_out 'first line' 'more line' 'even more line'.

manatwork
  • 31,277
  • 3
    Awesome.............. You are Master .... – Rahul Patil Mar 31 '13 at 08:29
  • How would I apply this function to multiple lines, or a single line with \n type CRs? – TryTryAgain Jun 02 '16 at 17:48
  • @manatwork that's marvelous, BUT... it's not working for expanding variables...and then if I try any quoting to resolve, it prints on one line again. Thank you so much for the quick response and brilliant solution! – TryTryAgain Jun 02 '16 at 19:17
  • 1
    Variable expansion should occur before the box_out function being executed. Double quotes and escapes should still work as usual: http://pastebin.com/ekmUKUkn By the way, you also use bash, right? – manatwork Jun 03 '16 at 07:20
  • It was the double quotes that did it! AMAZING :D – TryTryAgain Jun 03 '16 at 13:21
  • Would you please either provide a link or explain how this works? There is a lot of "magic" in there: ${s[@]}, ((w<${#l})), ${b//?/-}. – Andreas Storvik Strauman Apr 07 '18 at 08:45
  • 1
    @AndreasStorvikStrauman: ${s[@]} – all elements of array s; ((w<${#l}))((..)) shell arithmetic testing whether variable w's value is less than variable l value's length, obtained with parameter expansion (# – string length); ${b//?/-} – parameter expansion replacing all occurrences of any character with a “-” (// – replace all; ? – wildcard meaning any character). – manatwork Apr 07 '18 at 17:31
14

So, my solution is not quite the same as yours, but strictly speaking it prints a box around the text, and the implementation is a bit simpler so I thought I'd share.

banner() {
    msg="# $* #"
    edge=$(echo "$msg" | sed 's/./#/g')
    echo "$edge"
    echo "$msg"
    echo "$edge"
}

And here it is in action:

$ banner "hi"
######
# hi #
######
$ banner "hi there"
############
# hi there #
############

Just plain text, no fancy ansi colors or anything.

robru
  • 351
12

Boxes!

$ echo 'Love Unix & Linux' | boxes -d stone -p a2v1
+---------------------+
|                     |
|  Love Unix & Linux  |
|                     |
+---------------------+

Since boxes requires text to be sent to it as a file, I recommend using the syntax in the above example where the text is piped into boxes through echo.

"Why boxes?"

Using it is easy. Just tell it what border design you want to use and how you want it to appear, and you're done.

Of course if you want to be creative, you can make your own designs. It's really easy and fun to do. The only drawback with using boxes is that I haven't figured out how to center a box generated with boxes to align center with the screen, though there is a bash hack for that

"What about color?"

The original question had demonstrated the use of color codes. So it only seems right to show how boxes would handle this.

While tput is popular, I consider myself an old school Bash person and still like using the escape commands. I'm sure there are a few folks here at StackExchange that would argue against it, but to each their own. Regardless, I am willing to set aside my personal preference of doing this to include another example doing it the tput way.

Naturally, I think the first step is to set the color of the inside text. So let's do that first.

printf "$(tput setaf 4)Love Unix & Linux$(tput sgr 0)" | boxes -d stone -p a2v1
+---------------------------------+
|                                 |
|  Love Unix & Linux  |
|                                 |
+---------------------------------+

If this were in the terminal, Love Unix & Linux would be blue...however as you can see, boxes did not handle this well. So what wen't wrong?

printf "$(tput setaf 4)Love Unix & Linux$(tput sgr 0)" | boxes -d stone -p a2v1 | cat -A
+---------------------------------+$
|                                 |$
|  ^[[34mLove Unix & Linux^[(B^[[0m  |$
|                                 |$
+---------------------------------+$

A closer inspection by showing the hidden characters with cat -A shows boxes assumed the length of the box to include the length of text AND the escape characters.

It should be noted though, that if you use a program like lolcat outside of boxes, the output looks like this

$ echo 'Love Unix & Linux' | boxes -d stone -p a2v1 | lolcat -f
+---------------------+
|                     |
|  Love Unix & Linux  |
|                     |
+---------------------+

but with the box and text in rainbow colors.

It's a good thing I hadn't made my own border designs that included color codes in them as of yet, as I would imagine the border designs would be fallable to the same problems.

Centering Text, ASCII Art, and BOXES!!!

The other flaw boxes has is that if you know how to use Bash to center text to the with of the terminal, boxes will still align the box to the left of the screen. Figured it out.

When you want to center text that is not in a box, you can simply use

center(){
  COLS=$(tput cols)  # use the current width of the terminal.
  printf "%*s\n" "$(((${#1}+${COLS})/2))" "$1"
}

And use that to center a single line of text.

center 'Love Unix & Linux'

For ascii-art where multiple lines are used and must be fixed in place there is this option.

# Rather than removing text, even things out by padding lines with spaces
draw_banner(){
  local banner="$1"
  # Banner Width
  BW=$(cat $banner | awk '{print length}' | sort -nr | head -1)

  while IFS= read -r line; do
    line=$(echo "${line}" | sed -n -e 's/^  / /g;p')
    line=$(printf "%-${BW}s" "${line}")
    center "${line}"  # our center function from earlier.
  done < "$banner"
}

draw_banner "path/to/your_ascii_art_logo.txt"

But if you like to use things like figlet, you don't need to use those functions as the -c option provides an option to center.

$figfontdir="path/to/figlet/fonts"
$figfont="Alligator"
text_banner(){
  COLS=$(tput cols)  # use the width of the terminal!
  figlet -d "$figfontdir" -f "$figfont" -k -w $COLS -c "$1"
}

text_banner 'Love Unix & Linux'

For boxes, we do something similar to draw_banner() but we need to pipe the data!

# Center a box created with `boxes
# It's like draw_banner, but `<<<` reads a string of data rather than a file.
$boxfile="/path/to/your_box_designs.box" # or ".txt". I like ".box".
$boxdesgin="stone"
center_box(){
  local data="$(</dev/stdin)"  # Read from standard input
  # Banner Width
  BW=$(cat <<< ${data} | awk '{print length}' | sort -nr | head -1)

  while IFS= read -r line; do
    line=$(echo "${line}" | sed -n -e 's/^  / /g;p')
    line=$(printf "%-${BW}s" "${line}")
    center "${line}"  # our center command from earlier.
  done <<< "${data}"
}

(
 # A bunch of stuff to center!
) | boxes -f $boxfile -d $boxdesign -a hcvcjc | center_box

Outstanding issues

Fixing both of these issues the UTF/ANSI character issue would not only make boxes a better solution to encapsulating text in an ASCII box, but allow for a creative alternative that could be called upon instead of hand-coding boxes.

JRCharney
  • 121
  • One other thing I should mention: If you install lolcat DO NOT install it through apt-get, install it as a gem through Ruby. The Ruby gem version is newer and respects ANSI box characters. – JRCharney Sep 09 '18 at 05:07
1

Took @robru comment and improved it a bit to allow multi line text.

  function PP () {
  local longest=0
  local string_array=("${@}")
  for i in "${string_array[@]}"; do
    if [[ "${#i}" -gt "${longest}" ]]; then
      local longest=${#i}
      local longest_line="${i}" # Longest line
    fi
  done

local edge=$(echo "$longest_line" | sed 's/./#/g' | sed 's/^#/###/' | sed 's/#$/###/') local middle_edge=$(echo "$longest_line" | sed 's/./\ /g' | sed 's/^\ /#\ /' | sed 's/\ $/\ \ #/')

echo -e "\n${edge}" echo "${middle_edge}"

for i in "${string_array[@]}"; do local length_i=${#i} local length_ll="${#longest_line}" if [[ "${length_i}" -lt "${length_ll}" ]]; then printf "# " local remaining_spaces=$((length_ll-length_l)) printf "${i}" while [[ ${remaining_spaces} -gt ${#i} ]]; do printf " " local remaining_spaces=$((remaining_spaces-1)) done printf " #\n" else echo "# ${i} #" fi done

echo "${middle_edge}" echo -e "${edge}\n" }

So, If you write PP "Hi." "I'm Here" "Nice to meet you......" You'll get the following:

##########################
#                        #
# Hi.                    #
# I'm Here               #
# Nice to meet you...... #
#                        #
##########################
1

I just very recently used manatwork's multi-line box answer in my project, I modded it to where it can creates banners made of - with centered text, no edges and even take BASH color parameters (with an optional second one that changes the color of the text). I created labels for every bash color I want to use through my project (i.e. RED='\e[31m')

# Multi line banners are generated like `banner ${COLOR} ${OPTIONAL_SECONDCOLOR} "first line" "second line" "third line"`
function banner() {
    local s=("${@:3}") b w
    local second_color=$2
    if [[ ! "$second_color" =~ "\e[".* ]]; then #If the second color isn't declared, then make the function use only the first color declared
        local s=("${@:2}") b w
        local second_color=$1
    fi
for l in &quot;${s[@]}&quot;; do
    ((w&lt;${#l})) &amp;&amp; { b=&quot;         $l         &quot;; w=&quot;${#l}&quot;; }
done
echo -ne $1
echo &quot;-${b//?/-}-&quot;
for l in &quot;${s[@]}&quot;; do
    printf '         %s%*s%s         \n' &quot;$(echo -ne $second_color)&quot; &quot;-$w&quot; &quot;$l&quot; &quot;$(echo -ne $1)&quot;
done
echo -e &quot;-${b//?/-}-${NC}&quot;

}

Example usage:

banner ${LIGHT_PURPLE} "test here something line 1. SUPER LONG AND IT REALLY NEEDS TO BE LONG" "super duper long line 2" # Single Color option
banner ${LIGHT_PURPLE} ${RED} " This should be red now test here something line 1. SUPER LONG AND IT REALLY NEEDS TO BE LONG" "super duper long line 2" #Two Color Option

Results from code: The result from the banner code