fmt.Scanf ist die Format-String-Variante von fmt.Scan: Statt nur Werte durch Whitespace getrennt einzulesen, beschreibst du mit einem Format-String die erwartete Eingabestruktur — inklusive Literalen, Whitespace-Markern und Verben. Die Verben sind dieselben wie bei fmt.Printf, nur rückwärts angewandt: Wo Printf einen Wert in seine Textform schreibt, liest Scanf aus dem Text einen Wert. Das klingt elegant, ist in idiomatischem Go aber eher selten. Sobald die Eingabe komplexer wird als „eine Zahl, ein Trenner, noch eine Zahl", greift man üblicherweise zu bufio.Scanner plus strconv, zu text/scanner oder zu regexp. Die offizielle Referenz lebt unter pkg.go.dev/fmt#Scanf.
Signatur
fmt.Scanf liest aus os.Stdin und folgt dabei einem Format-String, der die erwartete Struktur beschreibt. Die Funktion gibt die Anzahl der erfolgreich gelesenen Items und einen Fehler zurück.
func Scanf(format string, a ...any) (n int, err error)Der Format-String beschreibt das Layout der Eingabe: welche Literale auftauchen, welche Verben zu welchen Werten parsen und wo Whitespace toleriert wird. Die variadischen Argumente sind — wie bei Scan — Pointer, in die geschrieben wird. Ein Argument ohne &-Adressoperator löst zur Laufzeit einen Fehler aus.
Format-Verben für Input
Scanf akzeptiert die meisten Verben aus dem Printf-Universum, allerdings in lesender Rolle. Die wichtigsten Verben für Eingaben:
%d— Dezimalzahl in einenint/int64/...%f— Fließkommazahl infloat32/float64%s— Token bis zum nächsten Whitespace in einenstring%t—bool(akzeptiert1,t,T,TRUE,true,Trueund ihre False-Varianten)%x— Hex-Zahl%c— einzelne Rune (auch Whitespace)%v— generisches Lesen anhand des Ziel-Typs; technisch erlaubt, in der Praxis selten genutzt
Nicht jedes Printf-Verb hat eine sinnvolle Scanf-Semantik. %T etwa gibt es nur als Ausgabe-Verb. Auch Breiten- und Genauigkeits-Angaben verhalten sich beim Lesen anders als beim Schreiben.
Literale im Format-String
Alles, was im Format-String kein Verb ist, gilt als Literal und muss im Input exakt so auftauchen. Schreibst du "Wert: %d", dann erwartet Scanf den Text Wert:, gefolgt von einem Space, gefolgt von einer Dezimalzahl.
package main
import "fmt"
func main() {
var n int
// Eingabe muss exakt mit "Wert: " beginnen.
_, err := fmt.Scanf("Wert: %d", &n)
if err != nil {
fmt.Println("Fehler:", err)
return
}
fmt.Println("Gelesen:", n)
}$ echo "Wert: 42" | go run literale.go
Gelesen: 42
$ echo "wert: 42" | go run literale.go
Fehler: input does not match formatDas ist gleichzeitig die Stärke und die Schwäche von Scanf: Format-Strings sind kompakt, aber jeder Schreibfehler im Input — ein fehlender Doppelpunkt, ein zusätzliches Zeichen — führt direkt zu einem Parser-Fehler. Für menschliche CLI-Eingaben ist das oft zu strikt.
Whitespace im Format-String
Ein Whitespace-Zeichen im Format-String (Space, Tab) matcht null oder mehr beliebige Whitespace-Zeichen im Input — auch Newlines. Das ist eine wichtige Abweichung von %s-Token-Logik: Ein Space im Format ist kein „genau ein Space", sondern ein „beliebige Whitespace-Sequenz".
package main
import "fmt"
func main() {
var a, b int
// Das Space zwischen %d und %d matcht jede Menge Whitespace.
_, err := fmt.Scanf("%d %d", &a, &b)
if err != nil {
fmt.Println("Fehler:", err)
return
}
fmt.Println(a, b)
}$ printf "1 2\n" | go run whitespace.go
1 2
$ printf "1\n\n\n2\n" | go run whitespace.go
1 2Diese Regel macht Scanf relativ tolerant gegenüber Formatierung — solange die Reihenfolge der Werte stimmt. Sie ist gleichzeitig der Grund, warum Newlines mit Scanf nicht zuverlässig als Datensatztrenner funktionieren: Wer zeilenweise lesen will, nimmt Scanln oder bufio.Scanner.
Pointer-Pflicht
Wie alle Lese-Funktionen der fmt-Familie braucht Scanf Pointer als Ziele. Die übergebenen Variablen müssen mit & adressiert werden, sonst gibt es einen Laufzeitfehler.
package main
import "fmt"
func main() {
var n int
// Falsch: kein Pointer.
_, err := fmt.Scanf("%d", n)
fmt.Println("Fehler:", err)
}$ echo 42 | go run pointer.go
Fehler: Scanf: argument 1 is not a pointer: intDiese Regel ist nicht verhandelbar — Scanf muss den Wert irgendwo hinschreiben, und das geht in Go nur über einen Pointer auf die Zielvariable.
Warum selten in der Praxis
In produktivem Go-Code begegnet einem fmt.Scanf selten. Der Grund ist nicht, dass die Funktion kaputt wäre, sondern dass die Standardbibliothek robustere Werkzeuge für die meisten Parsing-Probleme bietet:
text/scanner— ein vollständiger Tokenizer für Go-ähnliche Sprachen, mit Lookahead, Position-Tracking und Fehlerbehandlung. Die richtige Wahl, wenn man einen Mini-Parser baut.bufio.Scannerplusstrconv— die idiomatische Kombination für zeilenweises Einlesen mit anschließender Konvertierung. Klarere Fehlermeldungen, leichter zu testen.regexp— passt, wenn die Struktur komplex ist, optionale Teile oder Alternativen hat oder pro Feld validiert werden muss.
Scanf selbst ist eigentlich nur dann sinnvoll, wenn die Eingabe eine einzelne Zeile mit einer sehr klaren Struktur ist — etwa ein Tool, das key=value-Paare in fester Reihenfolge erwartet. Sobald Edge-Cases dazukommen — leere Felder, optionale Komponenten, Whitespace-Toleranz an unerwarteten Stellen — wird der Format-String fragiler als ein paar Zeilen strings.Split plus strconv.Atoi.
Vergleich zu Sscanf
fmt.Sscanf liest aus einem string statt aus os.Stdin. In der Praxis taucht Sscanf deutlich häufiger auf als Scanf — aus einem ganz pragmatischen Grund: Man hat den Input meist schon als string (aus einer Konfigurationsdatei, aus einem HTTP-Header, aus einem bufio.Scanner.Text()-Aufruf). Diese Trennung — erst lesen, dann parsen — erlaubt es, den String vorab zu inspizieren, zu validieren oder mit strings.TrimSpace zu normalisieren, bevor er an den Parser geht.
Bei Scanf gibt es diese Trennung nicht: Lesen und Parsen passieren in einem Schritt, und ein Fehler im Parser verbraucht trotzdem Bytes von Stdin. Wer sauber arbeiten will, liest erst mit bufio.Scanner eine Zeile, validiert sie, und parst dann notfalls mit Sscanf — nicht mit Scanf.
Praxis 1 — Datums-Eingabe im Format YYYY-MM-DD
Ein typisches Lehrbuch-Beispiel: Der Anwender soll ein Datum in einem festen Format eingeben. Drei Zahlen, getrennt durch Bindestriche. Genau das Pattern, für das Scanf gemacht wurde — Literal, Verb, Literal, Verb, Literal, Verb.
package main
import "fmt"
func main() {
var y, m, d int
fmt.Print("Datum (YYYY-MM-DD): ")
n, err := fmt.Scanf("%d-%d-%d", &y, &m, &d)
if err != nil {
fmt.Println("Fehler:", err)
return
}
fmt.Printf("Gelesen: %d Felder -> %04d-%02d-%02d\n", n, y, m, d)
}$ echo "2026-05-21" | go run datum-scanf.go
Datum (YYYY-MM-DD): Gelesen: 3 Felder -> 2026-05-21Das funktioniert — solange der Anwender sich an das Format hält. Production-Code würde an dieser Stelle aber nicht Scanf nehmen, sondern time.Parse("2006-01-02", input): Das liefert direkt einen time.Time-Wert, prüft die Gültigkeit (kein Monat 13, kein 30. Februar) und gibt eine aussagekräftige Fehlermeldung. Scanf prüft nur die syntaktische Struktur, nicht die semantische Korrektheit.
Praxis 2 — dieselbe Aufgabe ohne Scanf
Zum direkten Vergleich dieselbe Datums-Eingabe mit dem idiomatischen Go-Werkzeugkasten: bufio.Scanner für eine Zeile, strings.Split für die Komponenten, strconv.Atoi für die Konvertierung.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
sc := bufio.NewScanner(os.Stdin)
fmt.Print("Datum (YYYY-MM-DD): ")
if !sc.Scan() {
fmt.Println("keine Eingabe")
return
}
parts := strings.Split(strings.TrimSpace(sc.Text()), "-")
if len(parts) != 3 {
fmt.Println("Fehler: erwartet YYYY-MM-DD")
return
}
y, err1 := strconv.Atoi(parts[0])
m, err2 := strconv.Atoi(parts[1])
d, err3 := strconv.Atoi(parts[2])
if err1 != nil || err2 != nil || err3 != nil {
fmt.Println("Fehler: keine gültigen Zahlen")
return
}
fmt.Printf("Gelesen: %04d-%02d-%02d\n", y, m, d)
}$ echo "2026-05-21" | go run datum-bufio.go
Datum (YYYY-MM-DD): Gelesen: 2026-05-21
$ echo "kaputt" | go run datum-bufio.go
Datum (YYYY-MM-DD): Fehler: erwartet YYYY-MM-DDMehr Code, ja — aber jeder Schritt ist sichtbar und einzeln testbar. Die Fehlermeldungen sind aussagekräftiger (erwartet YYYY-MM-DD statt input does not match format), die Validierung kann pro Feld erfolgen, und die Logik lässt sich problemlos um Edge-Cases erweitern (Leerstring, abweichende Trenner, Whitespace am Zeilenende). Diese Verbosität ist nicht Ballast, sondern Robustheit.
Interessantes
Format-String mit Verben
Scanf nutzt dieselben Verben wie Printf (%d, %f, %s, %t, %x, %c) — nur rückwärts: aus Text in Werte.
Pointer-Pflicht
Argumente müssen Pointer sein. Ohne & gibt es einen Laufzeitfehler argument is not a pointer.
Whitespace = beliebige Sequenz
Ein Whitespace im Format matcht eine beliebige Whitespace-Sequenz im Input — auch Newlines, auch leere Sequenz.
Literale exakt
Nicht-Verben im Format-String müssen im Input zeichengenau vorkommen. Jede Abweichung scheitert mit input does not match format.
Selten in Production-Code
In idiomatischem Go begegnet Scanf einem kaum — fast immer gibt es robustere Alternativen für dieselbe Aufgabe.
text/scanner für Tokenizing
Für echte Mini-Parser mit Lookahead und Position-Tracking ist text/scanner die deutlich tragfähigere Grundlage.
time.Parse für Datum
Datums-Eingaben gehören in time.Parse — semantische Validierung, klare Fehler, direkt ein time.Time-Wert.
regexp für komplexe Strukturen
Sobald optionale Teile, Alternativen oder Capture-Gruppen ins Spiel kommen, ist regexp flexibler als jeder Scanf-Format-String.