0

In the nano text editor, I can pipe the selection into a command, and I quite often need to center text, so I came up with the following code

center() {
  str=$1
  # Strip leading and trailing whitespace from the string
  str=$(echo "$str" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
  str_len=${#str}
  margin=$(((80 - str_len) / 2))
  printf "%*s%s%*s\n" $margin "" "$str" $margin ""
}

But I don't know much about scripting so I'd like to know how I could improve this code.

Why are you linking to a bash solution? I don't use this shell, I just want it to run with sh

2 Answers2

0

Assuming you are using the bash shell (see the end of this answer if not using bash), you can strip flanking whitespace from the string in str more efficiently using standard variable expansions and the extended globbing pattern +([[:space:]]) (matches one or more space-like characters):

shopt -s extglob

str=$1 str=${str##+([[:space:]])} # strip at start str=${str%%+([[:space:]])} # strip at end

We would want to print the string right-justified to the column given by (w+s)/2, where w is the width of the terminal (outputted by tput cols, but you may also use $COLUMNS) and s is the length of the string (${#str}):

printf '%*s\n' "$(( ( $(tput cols) + ${#str} ) / 2 ))" "$str"

The function:

center () (
    shopt -s extglob
str=$1
str=${str##+([[:space:]])}
str=${str%%+([[:space:]])}

printf '%*s\n' "$(( ( $(tput cols) + ${#str} ) / 2 ))" "$str"

)

Note that the body of the function is a sub-shell. This is so that the setting of the extglob shell option does not spill over into the calling shell.

Without relying on extglob (will work in any sh):

center () {
    str=$(printf '%s\n' "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
    printf '%*s\n' "$(( ( $(tput cols) + ${#str} ) / 2 ))" "$str"
}

Replace $(tput cols) with 80 if you want to format this to a static 80-column width.

Kusalananda
  • 333,661
  • Thank you for your answer –  Dec 24 '22 at 11:00
  • Note that in bash and POSIX compliant sh/printf. ${#var} gives you the length in number of characters while printf '%*s' pads to a number of bytes, so it can only be used for single-byte single-width characters. – Stéphane Chazelas Dec 24 '22 at 12:04
-1

If you don't have multiple whitespaces concatenated in the middle, you may simplify the trimming with str=$(echo $1)

center () {
  str=$(echo $1)
  len=${#str}
  margin=$((($COLUMNS - len) / 2))
  printf "%${margin}s%s\n" " " "$str"
}

You don't need to append whitespace after $str.

user unknown
  • 10,482
  • the unquoted expansion of $1 would also process any glob patterns, so if your text is e.g. 2 * 3 + 5, or ** NOTE: blahblah **, you get some weird results. (well, just a list of filenames) – ilkkachu Dec 24 '22 at 08:37
  • 1
    Note that some shells don't initialise a COLUMNS variable. – Kusalananda Dec 24 '22 at 09:38