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.
Print a string with padding using fmt
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 |
Print text in aligned columns using text/tabwriter
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