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

3
  • If switching to vim is an option, it's just :center there (based on the textwidth aka tw setting). Commented Dec 24, 2022 at 8:22
  • 1
    Does this answer your question? Center text with printf Commented Dec 24, 2022 at 8:43
  • Regarding your most recent edit: bash answers were suggested as this is the most common shell in use, and you did not specify a particular shell in the question. Note also that my own answer did already have a sh-only part to it, which I hope I have now pointed out a bit more explicitly. Commented Dec 24, 2022 at 9:42

2 Answers 2

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.

2
  • Thank you for your answer Commented Dec 24, 2022 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. Commented Dec 24, 2022 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.

2
  • 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) Commented Dec 24, 2022 at 8:37
  • 1
    Note that some shells don't initialise a COLUMNS variable. Commented Dec 24, 2022 at 9:38

You must log in to answer this question.