📰 String padding in Go

introduction strings format

If you want your strings in Go to be printed in aligned columns, you need to add the padding, i.e., filling the string with whitespace characters at the beginning or end so that the output has a fixed number of characters. To do this, you can use special flags, like "%-10s", added to format “verbs” when formatting text. You can also use an external package or the built-in text/tabwriter to print the data in aligned columns.

Using the built-in fmt.Printf() function, you can set the format of the printed data using so-called verbs such as "%s". Moreover, there are several flags to improve the display of strings. One of them is the flag to add padding, which is the number specifying its size in the verb "%s", between "%" and "s". By default, the string is padded with spaces on the left (the text is right-justified). If you want right padding and left alignment, add the - flag before the padding size.

Examples:

format description
"%s" Standard string format
"%20s" Pad string with the spaces on the left (right-justified string)
"%-20s" Pad string with the spaces on the right (left-justified string)

Left padding example

// left padding
fmt.Printf("+%s+\n", strings.Repeat("-", 45))
fmt.Printf("| %20s | %20s |\n", "vegetables", "fruits")
fmt.Printf("|%s|\n", strings.Repeat("-", 45))
fmt.Printf("| %20s | %20s |\n", "potato", "strawberry")
fmt.Printf("+%s+\n", strings.Repeat("-", 45))

Output:

+---------------------------------------------+
|           vegetables |               fruits |
|---------------------------------------------|
|               potato |           strawberry |
+---------------------------------------------+

Right padding example

// right padding
fmt.Printf("+%s+\n", strings.Repeat("-", 45))
fmt.Printf("| %-20s | %-20s |\n", "vegetables", "fruits")
fmt.Printf("|%s|\n", strings.Repeat("-", 45))
fmt.Printf("| %-20s | %-20s |\n", "potato", "strawberry")
fmt.Printf("+%s+\n", strings.Repeat("-", 45))

Output:

+---------------------------------------------+
| vegetables           | fruits               |
|---------------------------------------------|
| potato               | strawberry           |
+---------------------------------------------+

Variable padding size

But what if we would like to print a large table with columns of different widths, whose size would adapt to the length of the strings? In this case, the padding size will be variable, but that is not a problem for the fmt.Printf() function. The example below shows how to do this.

package main

import (
    "fmt"
    "strings"
)

func printTable(table [][]string) {
    // get number of columns from the first table row
    columnLengths := make([]int, len(table[0]))
    for _, line := range table {
        for i, val := range line {
            if len(val) > columnLengths[i] {
                columnLengths[i] = len(val)
            }
        }
    }

    var lineLength int
    for _, c := range columnLengths {
        lineLength += c + 3 // +3 for 3 additional characters before and after each field: "| %s "
    }
    lineLength += 1 // +1 for the last "|" in the line

    for i, line := range table {
        if i == 0 { // table header
            fmt.Printf("+%s+\n", strings.Repeat("-", lineLength-2)) // lineLength-2 because of "+" as first and last character
        }
        for j, val := range line {
            fmt.Printf("| %-*s ", columnLengths[j], val)
            if j == len(line)-1 {
                fmt.Printf("|\n")
            }
        }
        if i == 0 || i == len(table)-1 { // table header or last line
            fmt.Printf("+%s+\n", strings.Repeat("-", lineLength-2)) // lineLength-2 because of "+" as first and last character
        }
    }
}

func main() {
    var table = [][]string{
        {"vegetables", "fruits", "rank"},
        {"potato", "strawberry", "1"},
        {"lettuce", "raspberry", "2"},
        {"carrot", "apple", "3"},
        {"broccoli", "pomegranate", "4"},
    }
    printTable(table)
}

Output:

+---------------------------------+
| vegetables | fruits      | rank |
+---------------------------------+
| potato     | strawberry  | 1    |
| lettuce    | raspberry   | 2    |
| carrot     | apple       | 3    |
| broccoli   | pomegranate | 4    |
+---------------------------------+

Most of the code is used to calculate the number of characters to print, and you will have no trouble understanding it if you carefully follow it. What interests us the most is the line:

fmt.Printf("| %-*s ", columnLengths[j], val)

An asterisk * in the format specifies that the padding size should be given as an argument to the fmt.Printf() function. In this case, the padding is columnLengths[j].

Rearrange the arguments

It is also possible to rearrange the arguments in the format string. The example below shows that by adding numeric values in square brackets in front of the formatting placeholders, we can control the order in which each argument occurs.

fmt.Printf("| %-[2]*[1]s ", val, columnLengths[j])

The [n] notation is especially useful if we have repeating values in the string format. In such a situation they can only be given as an argument once:

fmt.Printf("I'm a %[1]s, a great %[1]s\n", "programmer")


Summarized examples with the variable size padding:

format description
"%s" Standard string format
"%*s" Pad string with a variable number of spaces on the left (right-justified string)
"%-*s" Pad string with a variable number of spaces on the right (left-justified string)
"%-[2]*[1]s" As above but the first argument is the string, and the second, the padding size

The Go standard library also includes a useful text/tabwriter package that creates properly aligned tables from strings separated by \t characters. This type of formatting is particularly useful for creating command-line applications that print tabular data. In the example below, we initialize tabwriter.Writer by setting output to the standard output os.Stdout, \t as the padding character and 4 as the width of the tab character. For printing, the line cells should be concatenated into a single string separated by tabs. We do this using the strings.Join() function. The tabwriter.Writer requires every column cell to be tab-terminated, so we also add the \t for the last cell in the row.

package main

import (
    "fmt"
    "os"
    "strings"
    "text/tabwriter"
)

func printTable(table [][]string) {
    writer := tabwriter.NewWriter(os.Stdout, 0, 4, 0, '\t', 0)
    for _, line := range table {
        fmt.Fprintln(writer, strings.Join(line, "\t")+"\t")
    }
    writer.Flush()
}

func main() {
    var table = [][]string{
        {"vegetables", "fruits", "rank"},
        {"potato", "strawberry", "1"},
        {"lettuce", "raspberry", "2"},
        {"carrot", "apple", "3"},
        {"broccoli", "pomegranate", "4"},
    }
    printTable(table)
}

Output:

vegetables      fruits          rank
potato          strawberry      1
lettuce         raspberry       2
carrot          apple           3
broccoli        pomegranate     4

🖨️ Convert string to []byte or []byte to string in Go

Learn the difference between a string and a byte slice
introduction strings slice

🔟 Convert string to bool in Go

Learn how to parse a string as a bool
introduction strings bool

👯 Remove duplicate spaces from a string in Go

Learn how to remove all redundant whitespaces from a string
introduction strings regex