10

Newbie here. I'm looking for bash script to center and justify text. My script only works with one line of text. How would you improve it?

#!/bin/bash
COLS=$(tput cols)
while true; do
    clear
    echo -n "Type text "
    read text
    echo
    echo "Menu"
    echo "1) Right justify  "
    echo "2) Center "
    echo "3) Exit "
    echo
    echo -n "Choose [1-3]: "
    read opt
    echo
    case $opt in
        1) printf "%*s\n" $COLS "$text"
           break
        ;;
        2) printf "%*s\n" $[$COLS/2] "$text"
           break
        ;;      
        3) break
        ;;
        *)
        echo "Error. Press [1-3]"
        break
        ;;
    esac
done

7 Answers 7

9

As you have discovered, $COLUMNS is useful only in an interactive -i shell, so we use columns="$(tput cols)" instead.

The only issue I have is with the line below. It does not center text.
printf "%*s\n" $[$COLS/2] "$text"

Expanding on your work, here is a function to display centered text (from from a file). To call it within your script, use display_center "file.txt"

display_center(){
    columns="$(tput cols)"
    while IFS= read -r line; do
        printf "%*s\n" $(( (${#line} + columns) / 2)) "$line"
    done < "$1"
}

Note the use of ${#line} (akin to wc -m) to count the number of characters in the line. As long as you only need to display plain text without colors/formatting, then this should work fine.

Here is a function to display right-justified text (from a file) using your same implementation of printf.

display_right(){
    columns="$(tput cols)"
    while IFS= read -r line; do
        printf "%*s\n" $columns "$line"
    done < "$1"
}

You can also do similar things with tput and echo, but the example below is not so robust (i.e. will fail with long strings).

row=0
col=$(( ($(tput cols) - ${#text}) / 2))
tput clear
tput cup $row $col
echo "$text"

Also, you may want to consider using dialog or select to generate your menu. It would make your script significantly cleaner.
http://bash.cyberciti.biz/guide/Select_loop
https://serverfault.com/questions/144939/multi-select-menu-in-bash-script

4
  • What if I also want to add colors? May 26, 2021 at 5:23
  • This works for me better: done <<< "$1"
    – rafi
    Sep 3, 2021 at 8:54
  • What does the * in %*s signify? It looks like it causes printf to accept the padding size as an argument instead of hard-coding it in the string, but I have never seen it before and I didn't find it in any manuals. Edit: apparently this is part of normal C printf() functionality, stackoverflow.com/a/4133356/2954547. It turned out that it was in my man page for printf(3), and I had just missed it. Apr 17, 2022 at 0:30
  • Probably not accepted because it doesn't justify centered text. But it's a nice start and helpful nonetheless
    – shmup
    Jul 24, 2022 at 16:04
5
#!/usr/bin/awk -f
{
  z = 92 - length
  y = int(z / 2)
  x = z - y
  printf "%*s%s%*s\n", x, "", $0, y, ""
}

Input

hello world
alpha bravo charlie delta

Output

                                         hello world
                                  alpha bravo charlie delta
1
  • 4
    awk '{ z = '$(tput cols)' - length; y = int(z / 2); x = z - y; printf "%*s%s%*s\n", x, "", $0, y, ""; }'
    – galva
    Jun 8, 2016 at 23:20
5

I've been having fun with scripting for a while. I'm a carpenter and mechanic by trade so bare with me lol. I know this is an old question but I recently was looking for something similar to fancy up an overly complicated Arch installation script I've been working on and never found an answer that fit my needs. So I wrote a (probably hackish) function that gives a few options to simplify the formatting I was looking for. I figured I'd share in case anyone was interested. This is all done in zsh.

# Formatting using printf
# 
# _L == total length for 'L' and 'R' options
# tl == total length for 'C' option
# _c == center for 'C' option ( tl / 2 )
# fill == filler character or space by default
# 
# Default lengths declared at top of function
# 
# usage $) f_M [string] [L,R,C] [character count]
#       -S option:
#       $) f_M -S [character] [total length (- for default 'space')]
#        -3rd var ($3) is needed for filler character ($2) or
#         else ($2) is total length
# 
# 

f_M () {
    tl=72
    _c=36
    _L=16
    f_numread () {
        printf $1 | sed ':a;s/\B[0-9]\{3\}\>/,&/;ta'
    }
    f_C () {
        local x=$(printf $1 | wc -c)
        local y=$(expr $_c - $(expr $x / 2))
        local z=$(expr $tl - $y)
        local space=' '
        printf "%${y}s%-${z}s\n" "$space" "$1"
    }
    case $1 in
        -S)
            fill=' '
            if [[ -n $2 ]]; then
                if [[ -n $3 ]]; then
                    fill="$2"
                    if [[ $3 =~ ^-?[0-9]+$ ]]; then
                        tl=$3
                    fi
                else
                    if [[ $2 =~ ^-?[0-9]+$ ]]; then
                        tl=$2
                    fi
                fi
            fi
            printf "${fill}%.0s" {1..$tl}
            ;;
        *)
            if [[ $3 =~ ^-?[0-9]+$ ]]; then
                tl=$3
                _L=$3
                _c=$(expr $tl / 2)
            fi
            if [[ $1 =~ ^-?[0-9]+$ ]]; then
                local x=$(f_numread $1)
                case $2 in
                    L)
                        printf "%-${_L}s\n" "$x" ;;
                    R)
                        printf "%${_L}s\n" "$x" ;;
                    C)
                        f_C $x ;;
                    "")
                        printf '%s\n' $x
                esac
            else
                case $2 in
                    L)
                        printf "%-${_L}s\n" "$1" ;;
                    R)
                        printf "%${_L}s\n" "$1" ;;
                    C)
                        f_C $1 ;;
                    "")
                        printf '%s\n' $1
                esac
            fi
            ;;
    esac
}

Here's some examples of how I use it...

# Default character length is 72

[user@arch]$ printf '%s\n' "<$(f_M -S)>"
<                                                                        >

[user@arch]$ printf '%s\n' "<$(f_M -S = 50)>"
<==================================================>

# To change the 'fill' character(s), a 3rd argument is required
# So I set '-' to keep my default value
# There is probably a better way to do it but this fit my needs

[user@arch]$ printf '%s\n' "<$(f_M -S = -)>"
<========================================================================>

# Because of what I was trying to do and to get multiple uses out of
# one function, I use separate variables for 'C' than for 'L' and 'R'

[user@arch]$ printf '%s\n%s\n%s\n' "<$(f_M 'left' L 72)>" \
    "<$(f_M 'center' C)>" "<$(f_M 'right' R 72)>"
<left                                                                    >
<                                 center                                 >
<                                                                   right>

# It will also take a string that is only numbers and make
# it more "human readable"

[user@arch]$ num=12345678
[user@arch]$ printf '%s\n' "<$(f_M ${num} C 50)>"
<                    12,345,678                    >

And to get even more creative I can add another function that will automagically make a fancy text box using an array...

#!/bin/zsh

f_fancy_box () {
    local s=''
    for s in ${text[@]}; do
        case $s in
            000) printf '%b\n' "<$(f_M -S = -)>" ;;
            ---) printf '%b\n' "| $(f_M -S - 70) |" ;;
            *) printf '%b\n' "|$(f_M $s C)|" ;;
        esac
    done
}

text=(
"000"
" "
"It is hobbies like this"
"that keep me from ever"
"getting any sleep lol"
" "
"---"
" "
"But I think"
"it is worth it"
":P"
" "
"000"
)

f_fancy_box

exit

OUTPUT:

<========================================================================>
|                                                                        |
|                         It is hobbies like this                        |
|                         that keep me from ever                         |
|                          getting any sleep lol                         |
|                                                                        |
| ---------------------------------------------------------------------- |
|                                                                        |
|                               But I think                              |
|                             it is worth it                             |
|                                   :P                                   |
|                                                                        |
<========================================================================>

I hope somebody finds this useful!

EDIT: Changed printf '%s' in f_fancy_box to '%b'. According to the man page for printf, and to allow for character escapes, I think that's actually the appropriate command to use. Someone correct me if I'm wrong...

2

Without destroying line contents?

This solution will put text in the center of the line in which the cursor is currently on, without printing spaces around its borders. Its useful if you want to center text without destroying what is currently printed on the line.

Notice: Your shell must support ANSI escape sequences for his example to work.

#!/bin/bash
print_center(){
    local x
    local y
    text="$*"
    x=$(( ($(tput cols) - ${#text}) / 2))
    echo -ne "\E[6n";read -sdR y; y=$(echo -ne "${y#*[}" | cut -d';' -f1)
    echo -ne "\033[${y};${x}f$*"
}

# main()

# clear the screen, put the cursor at line 10, and set the text color
# to light blue.
echo -ne "\\033[2J\033[10;1f\e[94m"

# do it!
print_center 'A big blue title!'

The cursor will be left at the end of "title!" in this example. Use another ANSI sequence to relocate the cursor as you see fit.


Pros

  • Works over SSH
  • Can resize device buffer without centering issues (dynamic)
  • Does not destroy line contents
  • ASCII game / animation friendly.

Cons

  • Will not work if your terminal does not support ANSI escape sequences
  • Not intended for rolling log output.
  • Depends on bash

1

A function that receives text and centers it horizontally, an array can also be used to enter the text.

It works as follows, a string is passed to the function and it is saved in an array, then with a for loop the array is traversed, printing each line in the center of the terminal.

function centerText() {
    columns="$(tput cols)"
    declare -a text=()
    text+=($*)
    for((i=0;i<${#text[@]};i++)); do
        printf "%*s\n" $(( (${#text[i]} + columns) / 2)) "${text[i]}"
    done
}

Here is a example:

function centerText() {
    columns="$(tput cols)"
    declare -a text=()
    text+=($*)
    for((i=0;i<${#text[@]};i++)); do
        printf "%*s\n" $(( (${#text[i]} + columns) / 2)) "${text[i]}"
    done
}

declare -a text=(
'First'
'Second'
'Third'
)

centerText ${text[@]}
sleep 5

Output:

                                     First
                                     Second
                                     Third



P.D: Sorry if there is something wrongly worded, my native language is not English.

0

I was also struggling for aligning texts in my bash script so I found solution it is:

# echo -e "\t Hello World!"
0

To print text at center:

echo "Some Text" | sed  -e :a -e "s/^.\{1,$(tput cols)\}$/ & /;ta" | tr -d '\n' | head -c $(tput cols)

Output:

                                    Some Text                                   

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .