How to justify and center text in bash?
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
#!/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
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
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...