Ein Format-String in fmt ist mehr als nur das Verb. Zwischen dem % und dem Buchstaben am Ende steht ein kleines Mini-DSL, das Breite, Präzision, Ausrichtung und Vorzeichendarstellung steuert. Die vollständige Grammatik lautet %[flags][width][.precision]verb — alles zwischen % und Verb ist optional, doch wenn es da ist, steht es in genau dieser Reihenfolge.

Diese Modifikatoren sind der Unterschied zwischen einer zerfledderten Konsolenausgabe und einer sauber ausgerichteten Tabelle. Wer mit Go ernsthafte CLI-Werkzeuge schreibt, verbringt mehr Zeit mit %-12s und %8.2f als mit komplexer Geschäftslogik.

Die Grammatik im Detail

Der Aufbau folgt einer festen Reihenfolge: zuerst das Prozentzeichen, dann optional ein oder mehrere Flags, dann optional eine Width (Mindestbreite als Zahl), dann optional ein Punkt mit nachfolgender Precision, und schließlich zwingend ein Verb. Jedes Element ist für sich optional, aber die Reihenfolge ist fix. Auch der Punkt vor der Precision ist zwingend — %82f ist nicht dasselbe wie %8.2f.

Go grammar.go
package main

import "fmt"

func main() {
	fmt.Printf("|%d|\n", 42)
	fmt.Printf("|%5d|\n", 42)
	fmt.Printf("|%.2f|\n", 3.14159)
	fmt.Printf("|%8.2f|\n", 3.14159)
	fmt.Printf("|%-5d|\n", 42)
	fmt.Printf("|%-8.2f|\n", 3.14159)
}
Output
|42|
|   42|
|3.14|
|    3.14|
|42   |
|3.14    |

Die Pipes | am Rand sind ein Trick, um die Leerzeichen am Anfang oder Ende sichtbar zu machen.

Width — Mindestbreite

Die Width ist eine Zahl direkt vor dem Verb (oder vor dem Punkt der Precision) und gibt die minimale Breite des formatierten Werts an. Ist der Wert kürzer, wird mit Leerzeichen aufgefüllt; ist er länger, wird nicht abgeschnitten. Die Default-Ausrichtung ist rechtsbündig.

Go width.go
package main

import "fmt"

func main() {
	fmt.Printf("|%5d|\n", 1)
	fmt.Printf("|%5d|\n", 42)
	fmt.Printf("|%5d|\n", 12345)
	fmt.Printf("|%5d|\n", 1234567)

	fmt.Printf("|%10s|\n", "Go")
	fmt.Printf("|%10s|\n", "Golang")
}
Output
|    1|
|   42|
|12345|
|1234567|
|        Go|
|    Golang|

Beachte die vierte Zeile: 1234567 hat 7 Stellen, die Width war 5 — Go bricht hier nicht ab und kürzt auch nichts. Wer eine harte Obergrenze braucht, muss zur Precision greifen.

Precision — Nachkommastellen oder Max-Stringlänge

Die Precision steht hinter einem Punkt und hat zwei verschiedene Bedeutungen: Bei Floats die Anzahl der Nachkommastellen (%.2f rundet auf zwei Stellen). Bei Strings die maximale Anzahl Zeichen (%.5s schneidet nach 5 Zeichen ab). Bei Integern: minimale Anzahl Ziffern, aufgefüllt mit führenden Nullen.

Go precision.go
package main

import "fmt"

func main() {
	fmt.Printf("%.2f\n", 3.14159)
	fmt.Printf("%.0f\n", 3.7)
	fmt.Printf("%.6f\n", 1.0/3.0)

	fmt.Printf("%.5s\n", "Hello, World")
	fmt.Printf("%.3s\n", "Gopher")

	fmt.Printf("%.5d\n", 42)
}
Output
3.14
4
0.333333
Hello
Gop
00042

Bei Strings ist die Kürzung byte-basiert — Vorsicht bei Multibyte-Zeichen wie deutschen Umlauten, dort kann %.5s mitten in einer UTF-8-Sequenz schneiden.

Width + Precision kombiniert

Die Kombination %8.2f ist der Klassiker für Geldbeträge in Tabellen: 8 Zeichen Gesamtbreite, davon 2 Nachkommastellen. Damit stehen die Dezimalpunkte sauber untereinander.

Go kombiniert.go
package main

import "fmt"

func main() {
	fmt.Printf("|%8.2f|\n", 3.14159)
	fmt.Printf("|%8.2f|\n", 1234.5)
	fmt.Printf("|%8.2f|\n", 0.1)

	fmt.Printf("|%10.5s|\n", "Hello, World")
	fmt.Printf("|%10.5s|\n", "Hi")
}
Output
|    3.14|
| 1234.50|
|    0.10|
|     Hello|
|        Hi|

Die Dezimalpunkte in der Float-Spalte stehen exakt untereinander — genau das, was eine professionelle Tabellenausgabe braucht.

Default ist rechtsbündig. Mit dem Flag - (Minus direkt nach dem %) wird linksbündig ausgerichtet, das Padding wandert ans Ende. Das ist die übliche Wahl für textuelle Spalten wie Namen oder Bezeichnungen.

Go alignment.go
package main

import "fmt"

func main() {
	fmt.Printf("|%10s|\n", "Go")
	fmt.Printf("|%-10s|\n", "Go")
	fmt.Printf("|%-10s|\n", "Golang")

	fmt.Printf("|%-10s|%5d|\n", "Apfel", 3)
	fmt.Printf("|%-10s|%5d|\n", "Banane", 12)
	fmt.Printf("|%-10s|%5d|\n", "Kirsche", 144)
}
Output
|        Go|
|Go        |
|Golang    |
|Apfel     |    3|
|Banane    |   12|
|Kirsche   |  144|

- und 0 schließen sich gegenseitig aus — Zero-Padding ergibt bei Linksausrichtung keinen Sinn.

Flag 0: Zero-Padding

Das Flag 0 füllt die Width nicht mit Leerzeichen, sondern mit führenden Nullen auf. Sinnvoll ausschließlich für numerische Verben. Klassischer Anwendungsfall: Zeitanzeigen wie 09:05:03, Hex-Werte mit fester Breite (0x00ff), ID-Strings mit Padding (00042).

Go zero_padding.go
package main

import "fmt"

func main() {
	fmt.Printf("%05d\n", 42)
	fmt.Printf("%05d\n", 12345)

	h, m, s := 9, 5, 3
	fmt.Printf("%02d:%02d:%02d\n", h, m, s)

	fmt.Printf("0x%04x\n", 255)

	fmt.Printf("%08.2f\n", 3.14)
}
Output
00042
12345
09:05:03
0x00ff
00003.14

Das %02d-Muster ist eines der häufigsten überhaupt in Go-Code — überall, wo Uhrzeiten, Datumsteile oder fortlaufende IDs formatiert werden.

Flag +: Vorzeichen erzwingen

Default schreibt Go ein Minuszeichen vor negative Zahlen, aber kein Pluszeichen vor positive. Mit dem Flag + wird das Vorzeichen immer ausgegeben. Das ist nützlich in Tabellen mit gemischten Vorzeichen.

Go vorzeichen.go
package main

import "fmt"

func main() {
	fmt.Printf("%+d\n", 42)
	fmt.Printf("%+d\n", -7)
	fmt.Printf("%+d\n", 0)

	deltas := []int{3, -5, 12, 0, -1}
	for _, d := range deltas {
		fmt.Printf("Delta: %+4d\n", d)
	}
}
Output
+42
-7
+0
Delta:   +3
Delta:   -5
Delta:  +12
Delta:   +0
Delta:   -1

In der Delta-Tabelle stehen die Vorzeichen exakt untereinander, was den optischen Unterschied zwischen Zunahme und Abnahme sofort erkennbar macht.

Flag #: Alternate-Form

Das #-Flag aktiviert eine sogenannte „alternative" Darstellung, deren Bedeutung verb-abhängig ist. Bei Hex (%#x) wird das 0x-Präfix vorangestellt, bei Oktal (%#o) eine führende 0. Beim v-Verb (%#v) liefert es die Go-Syntax-Repräsentation.

Go alternate.go
package main

import "fmt"

func main() {
	fmt.Printf("%x\n", 255)
	fmt.Printf("%#x\n", 255)

	fmt.Printf("%o\n", 8)
	fmt.Printf("%#o\n", 8)

	type Punkt struct{ X, Y int }
	p := Punkt{3, 4}
	fmt.Printf("%v\n", p)
	fmt.Printf("%+v\n", p)
	fmt.Printf("%#v\n", p)
}
Output
ff
0xff
10
010
{3 4}
{X:3 Y:4}
main.Punkt{X:3, Y:4}

Die drei v-Varianten zeigen die Eskalationsstufen: knapp, mit Feldnamen, mit Typ und Go-Syntax. Für Logging in der Entwicklung ist %#v Gold wert.

Dynamische Width mit *

Wenn die Spaltenbreite erst zur Laufzeit feststeht — etwa weil man die längste Bezeichnung in einer Liste ermittelt hat —, kann man die Width nicht hartcodieren. Hier hilft das *: Es nimmt die Width-Angabe nicht aus dem Format-String, sondern aus einem Argument.

Go dynamische_width.go
package main

import "fmt"

func main() {
	width := 10
	fmt.Printf("|%*d|\n", width, 42)
	fmt.Printf("|%-*s|\n", width, "Go")

	digits := 3
	fmt.Printf("%.*f\n", digits, 3.14159)

	fmt.Printf("|%*.*f|\n", 10, 2, 3.14159)

	namen := []string{"Apfel", "Brombeere", "Kirsche"}
	maxLen := 0
	for _, n := range namen {
		if len(n) > maxLen {
			maxLen = len(n)
		}
	}
	for _, n := range namen {
		fmt.Printf("|%-*s|\n", maxLen, n)
	}
}
Output
|        42|
|Go        |
3.142
|      3.14|
|Apfel    |
|Brombeere|
|Kirsche  |

Das Muster maxLen ermitteln, dann mit %-*s ausrichten — die Standard-Technik für jede dynamische Tabellenausgabe in Go.

Tabellen-Ausgabe mit gemischten Spalten

Eine typische Aufgabe: eine Produktliste mit Name, Preis und Menge so ausgeben, dass die Spalten ausgerichtet sind.

Go tabelle.go
package main

import "fmt"

type Produkt struct {
	Name  string
	Preis float64
	Menge int
}

func main() {
	produkte := []Produkt{
		{"Apfel", 0.99, 42},
		{"Brombeere", 3.5, 7},
		{"Kirsche", 4.25, 120},
		{"Wassermelone", 8.9, 3},
	}

	fmt.Printf("%-12s %8s %5s\n", "Name", "Preis", "Menge")
	fmt.Println("------------ -------- -----")

	for _, p := range produkte {
		fmt.Printf("%-12s %8.2f %5d\n", p.Name, p.Preis, p.Menge)
	}
}
Output
Name            Preis Menge
------------ -------- -----
Apfel            0.99    42
Brombeere        3.50     7
Kirsche          4.25   120
Wassermelone     8.90     3

Die Dezimalpunkte stehen exakt untereinander. Für komplexere Tabellen lohnt sich der Wechsel zu text/tabwriter.

Progress-Bar mit dynamischer Breite

Ein klassischer Trick: eine Fortschrittsanzeige [####------] ohne Bibliothek bauen.

Go progressbar.go
package main

import (
	"fmt"
	"strings"
)

func progress(percent, width int) string {
	filled := width * percent / 100
	bar := strings.Repeat("#", filled)
	return fmt.Sprintf("[%-*s] %3d%%", width, bar, percent)
}

func main() {
	for _, p := range []int{0, 25, 50, 75, 100} {
		fmt.Println(progress(p, 30))
	}
}
Output
[                              ]   0%
[#######                       ]  25%
[###############               ]  50%
[######################        ]  75%
[##############################] 100%

Der Kern ist %-*s: linksbündig (-), Width aus Argument (*), String-Wert (s). Das Leerzeichen-Padding rechts vom #-Block ergibt die noch offene Strecke der Bar.

Interessantes

Reihenfolge ist fix

Die Grammatik %[flags][width][.precision]verb ist nicht verhandelbar — Width vor Precision, Punkt vor Precision-Zahl, Verb immer am Ende.

Width ist Mindestbreite

%5d reserviert mindestens 5 Zeichen; längere Werte werden nicht abgeschnitten. Für harte Obergrenzen bei Strings braucht es Precision.

Precision ist mehrdeutig

Bei Floats Nachkommastellen, bei Strings Maximalbreite, bei Integern Mindestziffern. Immer den Verb-Typ im Kopf behalten.

Default rechtsbündig

Ohne Flag richtet fmt rechtsbündig aus. Mit - linksbündig — typisch für Textspalten wie Namen.

Zero-Padding nur für Zahlen

0 füllt mit führenden Nullen — sinnvoll bei Uhrzeiten, IDs, Hex-Werten. - und 0 zusammen: 0 wird ignoriert.

+ erzwingt Vorzeichen

%+d zeigt + auch bei positiven Zahlen — ideal für Delta-Spalten mit gemischten Vorzeichen.

# ist verb-abhängig

%#x0x-Präfix, %#o → führende 0, %#v → Go-Syntax. Ein Flag, viele Bedeutungen.

* für dynamische Width

%*d und %.*f nehmen Width oder Precision aus einem Argument — Pflicht für generische Tabellenausgaben zur Laufzeit.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das fmt-Paket — Formatierte I/O

Zur Übersicht