Go · Golang

Center text in a terminal with Go.


This guide will show how simple it is to center text in a terminal or a text file using the Go standard and external libraries. We will create two functions, NCenter, which provides for a width parameter, and a Center func that will determine the center placement based on the width of the terminal screen.

You can find the final code on GitHub or play with NCenter() in the Go Playground.

NCenter()

The NCenter func takes a width and a string parameter and returns bytes.Buffer. This flexible Buffer value enables its usage with io.writers or String and Bytes methods.

func NCenter(width int, s string) *bytes.Buffer {}

Create two constants for clarity. The half value of 2 and a Unicode codepoint for a space character.

func NCenter(width int, s string) *bytes.Buffer {
    const half, space = 2, "\u0020"
}

Create an empty buffer of bytes that will hold our data.

func NCenter(width int, s string) *bytes.Buffer {
    const half, space = 2, "\u0020"
    var b bytes.Buffer
}

Determine the n integer value that is the number of space characters needed to center the s string.

func NCenter(width int, s string) *bytes.Buffer {
    const half, space = 2, "\u0020"
    var b bytes.Buffer
    n := (width - utf8.RuneCountInString(s)) / half
}

The width value is the container width for the text, often known as the column value.
We use RuneCount to determine the number of characters in the string. Remember len(s) returns the number of bytes, which is not helpful for this situation.
For example, if we have an 80 width value and want to center “Hello world! 👋” containing 14 characters.

n = (80 - 14) / 2 

n is equal to 33, meaning we need to prefix 33 spaces to align “Hello world! 👋” to an 80 column text area.

fmt.Fprintf(&b, "%s%s", strings.Repeat(space, n), s)

Formats and saves the s string to the b buffer. Note that we are also prefixing the s string with n number of repeated space characters.

func NCenter(width int, s string) *bytes.Buffer {
    const half, space = 2, "\u0020"
    var b bytes.Buffer
    n := (width - utf8.RuneCountInString(s)) / half
    fmt.Fprintf(&b, "%s%s", strings.Repeat(space, n), s)
    return &b
}

Finally, we can use our NCenter function.

package main

import (
	"bytes"
	"fmt"
	"strings"
	"unicode/utf8"
)

func main() {
	const s = "Hello world! 👋"
	fmt.Println(NCenter(80, s))
}

func NCenter(width int, s string) *bytes.Buffer {
	const half, space = 2, "\u0020"
	var b bytes.Buffer
	n := (width - utf8.RuneCountInString(s)) / half
	fmt.Fprintf(&b, "%s%s", strings.Repeat(space, n), s)
	return &b
}
$ go run .
                                 Hello world! 👋                                 

The NCenter is now working but has a significant flaw. Go panics if the supplied width parameter matches zero or is less than the number of characters in the string. The s string contains 14 characters, but if we attempt to center it to a text width of 10, it results in an invalid negative n value.

func main() {
    const s = "Hello world! 👋"
    fmt.Println(NCenter(10, s))
}
$ go run .
panic: strings: negative Repeat count

To avoid this flaw, we must only use strings.Repeat if the n value is greater than zero. Otherwise, we return the string with as-is.

func NCenter(width int, s string) *bytes.Buffer {
    const half, space = 2, "\u0020"
    var b bytes.Buffer
    n := (width - utf8.RuneCountInString(s)) / half
	if n < 1 {
		fmt.Fprint(&b, s)
		return &b
	}
    fmt.Fprintf(&b, "%s%s", strings.Repeat(space, n), s)
    return &b
}
$ go run .
Hello world! 👋

Center()

The Center func will determine a width int value for NCenter and return the result. The width value will either be the number of columns of the active terminal screen or zero. As of writing, Go v1.17 cannot determine the column count of PowerShell or Windows CMD shells.

func Center(s string) *bytes.Buffer {}

We now need the file descriptor integer value for the operating system’s standard input.

func Center(s string) *bytes.Buffer {
    fd := int(os.Stdin.Fd())
}

Finally, we can use the extended term library’s GetSize function to obtain the w width of our active terminal window. If an error occurs getting this, then pass on a zero-width value.

func Center(s string) *bytes.Buffer {
    fd := int(os.Stdin.Fd())
	w, _, err := term.GetSize(fd)
	if err != nil {
		return NCenter(0, s)
	}
	return NCenter(w, s)
}

Let’s test it out.

func main() {
	const s = "Hello world! 👋"
	fmt.Println(Center(s))
}
$ go run .
go run output on a macOS bash terminal window showing the use of Center().
macOS iTerm2 terminal.

Final code

package main

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"unicode/utf8"

	"golang.org/x/term"
)

func main() {}

// NCenter centers the string to the column width.
func NCenter(width int, s string) *bytes.Buffer {
	const half, space = 2, "\u0020"
	var b bytes.Buffer
	n := (width - utf8.RuneCountInString(s)) / half
	if n < 1 {
		fmt.Fprintf(&b, s)
		return &b
	}
	fmt.Fprintf(&b, "%s%s", strings.Repeat(space, int(n)), s)
	return &b
}

// Center the string to the width of the terminal.
// When the width is unknown, the string is left-aligned.
func Center(s string) *bytes.Buffer {
	fd := int(os.Stdin.Fd())
	w, _, err := term.GetSize(fd)
	if err != nil {
		return NCenter(0, s)
	}
	return NCenter(w, s)
}

Various uses

func main() {
	const s = "Hello world! 👋"

	// print to the terminal
	buf := Center(s)
	fmt.Println(buf)
	fmt.Fprintln(os.Stdout, buf)
	io.Copy(os.Stdout, buf)

	// save to a text file
	buf = NCenter(80, s)
	if err := os.WriteFile("hi.txt", buf.Bytes(), 0666); err != nil {
		log.Fatal(err)
	}
}
go run output on a macOS bash terminal window showing three different ways to print to standard output.
macOS iTerm2 terminal.
TextEdit displaying the hi.txt file that's created by os.WriteFile.
macOS TextEdit displaying the hi.txt file.
go run output on Windows Terminal showing three different ways to print to standard output.
Windows Terminal running a PowerShell tab in the top pane and a Ubuntu bash tab in the bottom pane.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s