Von allen Scan-Funktionen im fmt-Paket ist Sscanf die, die im Alltag am häufigsten den richtigen Schnitt trifft. Sie kombiniert zwei Bedürfnisse, die in der Praxis fast immer gemeinsam auftreten: Man hat den Input bereits als String im Speicher — gelesen aus einer Datei, einer HTTP-Antwort, einer Log-Zeile, einem Konfigurationswert — und man möchte ihn nicht einfach in einzelne Tokens zerlegen, sondern strukturiert nach einer bekannten Form parsen. Das S im Namen steht für String als Quelle, das f für den Format-String, der die erwartete Struktur beschreibt.

Damit deckt Sscanf einen Bereich ab, der zwischen primitivem strings.Split und schwergewichtigem regexp liegt. Sobald der Input einem festen Muster folgt — key=value-Paaren, einem host:port-Schema, einem ISO-ähnlichen Datum — ist Sscanf oft die kürzeste, gut lesbare Lösung. Es ist allerdings ein scharfes, aber begrenztes Werkzeug: Sobald das Muster Varianten kennt, optionale Felder hat oder echte Zeichenklassen braucht, ist regexp der bessere Griff, und bei Datums- und Zeitwerten ohnehin immer time.Parse. Die offizielle Referenz: pkg.go.dev/fmt#Sscanf.

Im Folgenden gehen wir die Signatur, die Verben und das Verhalten bei Literalen und Whitespace im Detail durch, und sehen uns danach an, wo Sscanf glänzt und wo man besser zu spezialisierten Werkzeugen greift.

Signatur

func Sscanf(str string, format string, a ...any) (n int, err error)

Die Funktion nimmt drei Bestandteile entgegen, die jeweils eine klar getrennte Rolle spielen. Der erste Parameter str ist der zu parsende Input — ein einfacher String, der irgendwo in der Anwendung schon vorliegt. Der zweite Parameter format ist der Format-String, also die Schablone, gegen die str gematcht wird. Dieser Format-String mischt Verben (%d, %s, …) mit Literalen (:, =, port= …); die Verben markieren die Stellen, an denen Werte extrahiert werden, die Literale müssen exakt im Input vorkommen.

Der dritte Teil, a ...any, ist eine variadische Liste von Pointern auf die Zielvariablen. Für jedes Verb im Format-String erwartet Sscanf exakt einen Pointer in derselben Reihenfolge. Der Rückgabewert n gibt an, wie viele Werte erfolgreich gescannt wurden, err meldet alles, was abweicht: zu wenig Input, fehlendes Literal, falscher Typ, unerwartetes Ende.

Format-Verben für Input

Die Verben in Sscanf sehen aus wie die von Printf, lesen sich aber rückwärts: Statt einen Wert zu formatieren, beschreiben sie, in welchem Format ein Wert im Input erwartet wird. Die wichtigsten Verben für die meisten Anwendungsfälle sind %d für ganze Zahlen, %f für Gleitkommazahlen, %s für eine Sequenz von Nicht-Whitespace-Zeichen, %t für Boolean (true/false), %x für hexadezimale Ganzzahlen und %c für ein einzelnes Unicode-Zeichen.

Go verben.go
package main

import "fmt"

func main() {
	var n int
	var f float64
	var s string
	var b bool
	var h int
	var c rune

	fmt.Sscanf("42", "%d", &n)
	fmt.Sscanf("3.14", "%f", &f)
	fmt.Sscanf("hallo", "%s", &s)
	fmt.Sscanf("true", "%t", &b)
	fmt.Sscanf("ff", "%x", &h)
	fmt.Sscanf("A", "%c", &c)

	fmt.Printf("n=%d f=%g s=%q b=%t h=%d c=%c\n", n, f, s, b, h, c)
}
Output
n=42 f=3.14 s="hallo" b=true h=255 c=A

Wichtig ist die Konsistenz: Das Verb bestimmt, wie der Wert im Input interpretiert wird, der Pointer-Typ muss zum Verb passen. Ein %d mit einem *float64 ist ein Compile-Fehler nicht — aber ein Laufzeit-Fehler, weil Sscanf den Typ-Mismatch zur Run-Time erkennt und in err zurückmeldet.

Literale im Format matchen

Der eigentliche Charme von Sscanf liegt in den Literalen zwischen den Verben. Ein Format-String wie "server=%s port=%d" beschreibt nicht nur, dass irgendwo ein String und eine Zahl vorkommen — er verlangt, dass der Input genau diese Form hat, mit dem Literal server= davor, einem Leerzeichen-Block dazwischen und port= vor der Zahl. Stimmt eines dieser Literale nicht, schlägt das Scannen fehl.

Go literale.go
package main

import "fmt"

func main() {
	input := "server=db01 port=5432"

	var host string
	var port int
	n, err := fmt.Sscanf(input, "server=%s port=%d", &host, &port)

	fmt.Printf("n=%d err=%v\n", n, err)
	fmt.Printf("host=%q port=%d\n", host, port)

	// Falsches Literal: kleiner Tippfehler, großer Effekt
	_, err2 := fmt.Sscanf(input, "Server=%s port=%d", &host, &port)
	fmt.Printf("falsch: err=%v\n", err2)
}
Output
n=2 err=<nil>
host="db01" port=5432
falsch: err=input does not match format

Diese Strenge ist die eigentliche Stärke: Sscanf ist kein Best-Effort-Parser. Entweder passt der Input zur Form, oder das Parsen schlägt sauber fehl. Das macht den Code lesbar und macht Fehlerfälle früh sichtbar, statt sie in halb gefüllte Variablen abwandern zu lassen.

Whitespace im Format

Eine Eigenheit, die man kennen muss: Jeder Whitespace im Format-String — egal ob ein einzelnes Leerzeichen oder mehrere Spaces hintereinander — matched im Input ein oder mehrere beliebige Whitespace-Zeichen. Das schließt Spaces, Tabs und Newlines ein. Diese Flexibilität ist meist hilfreich, weil reale Eingaben aus Logs oder Protokollen mal mit einem, mal mit mehreren Leerzeichen formatiert sind.

Go whitespace.go
package main

import "fmt"

func main() {
	// Ein Space im Format, aber mehrere Tabs/Spaces im Input
	var a, b int
	n, err := fmt.Sscanf("42    \t  7", "%d %d", &a, &b)
	fmt.Printf("n=%d err=%v a=%d b=%d\n", n, err, a, b)
}
Output
n=2 err=<nil> a=42 b=7

Die Kehrseite davon: Wenn man exakt einen Trenner verlangen will (etwa einen einzelnen Doppelpunkt zwischen Host und Port), darf zwischen den Verben kein Leerzeichen stehen, sondern muss das Literal direkt anschließen — "%s:%d" statt "%s :%d". Diese Unterscheidung ist subtil, aber entscheidend für robustes Parsen.

Pointer-Pflicht

Wie alle Scan-Funktionen schreibt Sscanf die geparsten Werte in die übergebenen Variablen. Das funktioniert in Go nur über Pointer, weil Argumente sonst per Wert kopiert werden und das Original unverändert bleibt. Jedes Argument nach dem Format-String muss daher ein &variable sein. Vergisst man das &, kompiliert der Code zwar, schlägt aber zur Laufzeit mit einer aussagekräftigen Fehlermeldung fehl, weil Sscanf per Reflection prüft, ob die Argumente Pointer sind.

Go pointer.go
package main

import "fmt"

func main() {
	var x int
	// Korrekt: Pointer übergeben
	fmt.Sscanf("123", "%d", &x)
	fmt.Println("x =", x)

	// Falsch: Wert statt Pointer
	var y int
	_, err := fmt.Sscanf("123", "%d", y)
	fmt.Println("err:", err)
}
Output
x = 123
err: Sscanf: argument 1: expected pointer; got int

Wann besser regexp oder time.Parse

Sscanf ist ein scharfes Werkzeug — aber es ist linear und positionsgebunden. Sobald das Eingabeformat Varianten kennt (optionale Felder, alternative Trenner, sich wiederholende Gruppen), wird der Format-String unbrauchbar, weil er keinen Mechanismus für Alternativen und keine echten Zeichenklassen kennt. In diesen Fällen ist regexp die richtige Wahl: Mit Capture-Gruppen, Quantifizierern und Alternativen lassen sich auch komplexe Strukturen ausdrücken — auf Kosten von Lesbarkeit und Geschwindigkeit.

Für Datums- und Zeitwerte sollte man Sscanf praktisch nie verwenden. Datum sieht oberflächlich nach Format-String-Parsing aus, ist aber ein Gebiet voller Sonderfälle: Zeitzonen, Lokale, Schaltjahre, RFC-Formate, abgekürzte Monatsnamen. Dafür gibt es time.Parse — eine spezialisierte Funktion mit eigener Schablonen-Sprache (2006-01-02 15:04:05), die genau diese Komplexität sauber abdeckt. Ein per Sscanf geparstes Datum mag im Test funktionieren und in Produktion an einer Zeitzone scheitern.

Die Faustregel lautet: Sscanf für strukturierten, simplen, vorhersehbaren Input; regexp für komplexe Muster mit Alternativen; time.Parse für alles, was nach Datum, Uhrzeit oder Zeitstempel aussieht.

Praxis 1 — IP-Adresse und Port parsen

Ein typischer Anwendungsfall: Aus einer Log-Zeile oder einer Konfigurationszeile soll eine host:port-Kombination herausgezogen werden, bei der der Host eine IPv4-Adresse ist. Für diesen einfachen Fall — ohne IPv6, ohne Hostnamen, ohne CIDR — ist Sscanf die kürzeste Lösung.

Go ip_port.go
package main

import "fmt"

func main() {
	input := "192.168.1.1:8080"

	var a, b, c, d, port int
	n, err := fmt.Sscanf(input, "%d.%d.%d.%d:%d", &a, &b, &c, &d, &port)
	if err != nil {
		fmt.Println("Parse-Fehler:", err)
		return
	}

	fmt.Printf("n=%d  ip=%d.%d.%d.%d  port=%d\n", n, a, b, c, d, port)
}
Output
n=5  ip=192.168.1.1  port=8080

Der Format-String "%d.%d.%d.%d:%d" beschreibt das Muster fast eins-zu-eins: vier durch Punkte getrennte Zahlen, ein Doppelpunkt, eine weitere Zahl. Was hier elegant aussieht, hat allerdings Grenzen — die Funktion validiert nicht, dass die Oktette zwischen 0 und 255 liegen, akzeptiert auch 999.999.999.999:99999 ohne Murren. Für produktiv genutzte Konfigurations-Parser ist deshalb net.SplitHostPort gefolgt von net.ParseIP die bessere Wahl. Sscanf ist hier ein gutes Werkzeug für interne Tools, Tests und CLI-Quick-Parser, in denen der Input bereits aus vertrauenswürdiger Quelle kommt.

Praxis 2 — Datum: Sscanf versus time.Parse

Das zweite Beispiel zeigt genau die Grenze, an die wir oben angedeutet haben. Ein ISO-ähnliches Datum lässt sich mit Sscanf zerlegen — aber sobald man mit dem Datum etwas tun will (Differenzen bilden, lokalisieren, in einer anderen Zeitzone interpretieren), wird klar, warum time.Parse der idiomatische Weg ist.

Go datum.go
package main

import (
	"fmt"
	"time"
)

func main() {
	input := "2026-05-22"

	// Variante A: Sscanf — zerlegt nur in drei Ints
	var y, m, d int
	if _, err := fmt.Sscanf(input, "%d-%d-%d", &y, &m, &d); err != nil {
		fmt.Println("Sscanf-Fehler:", err)
		return
	}
	fmt.Printf("Sscanf:    Jahr=%d Monat=%d Tag=%d\n", y, m, d)

	// Variante B: time.Parse — liefert eine echte time.Time
	t, err := time.Parse("2006-01-02", input)
	if err != nil {
		fmt.Println("time.Parse-Fehler:", err)
		return
	}
	fmt.Printf("time.Parse: %s (Wochentag: %s)\n", t.Format(time.RFC1123), t.Weekday())
}
Output
Sscanf:    Jahr=2026 Monat=5 Tag=22
time.Parse: Fri, 22 May 2026 00:00:00 UTC (Wochentag: Friday)

Die Sscanf-Variante liefert drei int-Werte — nicht mehr und nicht weniger. Wer damit weiterarbeiten will, muss daraus selbst per time.Date(...) ein time.Time bauen, und damit beginnt die Sonderfall-Hölle: Zeitzone? Lokal oder UTC? Was, wenn der Monat 13 ist? time.Parse dagegen liefert direkt ein voll funktionsfähiges time.Time zurück, kennt die Go-typische Referenz-Schablone 2006-01-02 15:04:05 und validiert ganz nebenbei. Für alles, was Datum oder Uhrzeit ist, ist time.Parse der richtige Weg — Sscanf ist hier nur eine Notlösung für Sonderformate, die time.Parse nicht kennt.

Häufigste praktische Scan-Variante

Wenn der Input schon als String vorliegt und nach einer festen Form geparst werden soll, ist Sscanf fast immer die kürzeste Lösung.

Format-String wie Printf, nur rückwärts

Die Verben sind dieselben, aber statt Werte zu formatieren beschreiben sie, in welcher Form Werte im Input erwartet werden.

Pointer-Pflicht

Jedes Ziel-Argument muss ein &variable sein; sonst greift die Reflection-Prüfung zur Laufzeit.

Whitespace ist flexibel

Ein einzelnes Leerzeichen im Format matched eine oder mehrere Whitespaces im Input — Spaces, Tabs, Newlines.

Literale müssen exakt matchen

Stimmt ein Literal im Format nicht mit dem Input überein, schlägt Sscanf mit input does not match format fehl — sauberer Fehlerpfad statt halb gefüllter Variablen.

Datum: time.Parse statt Sscanf

Für Datums- und Zeitwerte ist time.Parse die richtige Wahl — kennt Zeitzonen, Wochentage, Standardformate.

Komplexe Muster: regexp

Sobald Alternativen, optionale Felder oder echte Zeichenklassen ins Spiel kommen, ist regexp flexibler — auf Kosten von Lesbarkeit und Geschwindigkeit.

Test-Helpers

In Tests ist Sscanf oft idiomatisch, um erzeugte Outputs schnell zurück in Werte zu zerlegen und gegen Erwartungen zu prüfen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht