fmt.Sscanln ist die zeilenorientierte Variante von fmt.Sscan: Sie liest aus einem String, parst whitespace-getrennte Tokens in die übergebenen Pointer — und stoppt zwingend am ersten Newline. Alles, was nach \n im Input folgt, wird verworfen. Damit ist Sscanln das Werkzeug der Wahl, wenn aus einem mehrzeiligen Puffer gezielt nur die erste Zeile strukturiert ausgewertet werden soll: Log-Header, Konfig-Anker, Protokoll-Frames. Die vollständige Referenz findest du auf pkg.go.dev/fmt#Sscanln.
Signatur und Semantik
Die Funktion ist im Paket fmt deklariert und folgt dem gleichen Pointer-Vertrag wie alle Scan-Varianten:
func Sscanln(str string, a ...any) (n int, err error)str ist der zu parsende Input-String, a eine variadische Liste von Pointern auf die Zielvariablen. Zurückgegeben werden die Anzahl erfolgreich gefüllter Argumente sowie ein Fehler, falls die Eingabe nicht zum erwarteten Muster passt. Der entscheidende Unterschied zu Sscan liegt nicht in der Signatur, sondern im Verhalten: Sscanln behandelt den Newline als harte Grenze.
Stopp am ersten Newline
Sscanln scannt Tokens, bis entweder alle Argumente gefüllt sind oder der erste \n im String erreicht ist. Folgezeilen sind für die Funktion nicht existent — sie werden nicht gelesen, nicht geprüft, nicht zurückgegeben. Das macht Sscanln deterministisch für zeilenweise Verarbeitung: Egal wie groß der Input-Puffer ist, geparst wird immer nur das erste Stück bis zum Zeilenende.
package main
import "fmt"
func main() {
input := "42 hallo\nzweite zeile wird ignoriert\ndritte auch"
var n int
var wort string
count, err := fmt.Sscanln(input, &n, &wort)
fmt.Printf("gelesen: %d Argumente, err=%v\n", count, err)
fmt.Printf("n=%d, wort=%q\n", n, wort)
}gelesen: 2 Argumente, err=<nil>
n=42, wort="hallo"Obwohl der String drei Zeilen enthält, sieht Sscanln nur die erste. Die Folgezeilen sind funktional unsichtbar. Das ist exakt das gewünschte Verhalten, wenn ein mehrzeiliger Block einen strukturierten Kopf hat, dem freier Text folgt.
Strict-Mode innerhalb der Zeile
Innerhalb der ersten Zeile ist Sscanln strikt: Befinden sich nach den erfolgreich geparsten Tokens noch weitere, nicht konsumierte Tokens vor dem Newline, liefert die Funktion einen Fehler. Das unterscheidet sie deutlich von Sscan, das überschüssige Tokens schweigend stehen lässt.
package main
import "fmt"
func main() {
// Erwartet: 2 Tokens — geliefert: 3 auf einer Zeile
input := "42 hallo extra\n"
var n int
var wort string
count, err := fmt.Sscanln(input, &n, &wort)
fmt.Printf("count=%d\n", count)
fmt.Printf("err=%v\n", err)
}count=2
err=expected newlineDie beiden Argumente werden gefüllt, der Fehler expected newline signalisiert: Nach dem letzten erwarteten Token muss unmittelbar ein Newline folgen — kein weiteres Token. Diese Strenge ist ein Feature, kein Bug. Sie zwingt Aufrufer, das Zeilenformat exakt zu spezifizieren, und schützt vor stillem Datenverlust.
Sscan vs. Sscanln vs. Sscanf
Die drei Sscan-Familien-Funktionen unterscheiden sich in zwei Dimensionen: Wie wird die Eingabe begrenzt, und wie wird das Format spezifiziert?
| Funktion | Zeilen-Stopp | Format-String | Extra Tokens | Use Case |
|---|---|---|---|---|
Sscan | nein | nein | erlaubt, werden ignoriert | beliebige whitespace-Liste |
Sscanln | ja (\n) | nein | Fehler | strukturierte Einzelzeile |
Sscanf | format-getrieben | ja | format-abhängig | präzises Layout |
Sscanln sitzt also in der Mitte: zeilenbasiert wie Scanln, aber ohne Format-String — passend für einfache, whitespace-separierte Header-Formate.
Use Case: Den Kopf eines mehrzeiligen Blocks lesen
Sscanln glänzt überall dort, wo ein Datenblock aus einem strukturierten Header und einem freien Rumpf besteht: Log-Einträge mit Header-Zeile und Stacktrace darunter, Konfig-Dateien mit Versionszeile gefolgt von Sektionen, Protokoll-Frames mit Steuerzeile und Payload. Statt den Block selbst zu zerlegen, übergibst du den ganzen Puffer an Sscanln — und die Funktion isoliert den Header für dich.
Log-Header aus mehrzeiligem Eintrag extrahieren
Ein typischer Log-Eintrag besteht aus einer maschinenlesbaren Header-Zeile (Zeitstempel, Level, Modul) und einem mehrzeiligen Body (Message, Stacktrace, Kontext). Sscanln liest exakt den Header.
package main
import "fmt"
func main() {
logEintrag := `2026-05-22T08:14:03Z ERROR auth.session
session token expired for user 4711
context: ip=192.168.1.42, agent=curl/8.4.0
stacktrace:
at validateToken (session.go:142)
at handleRequest (handler.go:88)`
var zeit, level, modul string
n, err := fmt.Sscanln(logEintrag, &zeit, &level, &modul)
if err != nil {
fmt.Println("Header-Parse-Fehler:", err)
return
}
fmt.Printf("Felder: %d\n", n)
fmt.Printf("Zeit: %s\n", zeit)
fmt.Printf("Level: %s\n", level)
fmt.Printf("Modul: %s\n", modul)
}Felder: 3
Zeit: 2026-05-22T08:14:03Z
Level: ERROR
Modul: auth.sessionDer Stacktrace und der Kontext darunter sind für Sscanln unsichtbar — die Funktion liefert nur die drei Header-Felder. Will man den Body danach weiter verarbeiten, holt man ihn separat via strings.SplitN(logEintrag, "\n", 2)[1].
Konfig-Anker: Version und Datum aus erster Zeile
Viele textbasierte Konfig-Formate beginnen mit einer Ankerzeile, die das Format identifiziert: Versionsnummer, Erstellungsdatum, vielleicht ein Schema-Marker. Der Rest der Datei folgt eigenen Regeln. Sscanln extrahiert den Anker und überlässt den Rest dem eigentlichen Konfig-Parser.
package main
import "fmt"
func main() {
konfig := `mibeon-config v3.2 2026-05-22
[server]
host = localhost
port = 8080
[database]
dsn = postgres://localhost/app`
var marker, version, datum string
n, err := fmt.Sscanln(konfig, &marker, &version, &datum)
if err != nil {
fmt.Println("Anker fehlerhaft:", err)
return
}
if marker != "mibeon-config" {
fmt.Println("unbekanntes Format:", marker)
return
}
fmt.Printf("Anker erkannt (%d Felder)\n", n)
fmt.Printf("Version: %s\n", version)
fmt.Printf("Datum: %s\n", datum)
}Anker erkannt (3 Felder)
Version: v3.2
Datum: 2026-05-22Das Muster ist robust: Schlägt der Anker fehl, brichst du sofort ab, ohne überhaupt den Konfig-Body anzufassen. Schlanker geht eine Format-Validierung kaum.
Stopp am ersten Newline
Sscanln liest exakt bis zum ersten \n im Input-String und ignoriert alles danach — der Rest des Puffers ist für die Funktion unsichtbar.
Pointer-Pflicht
Alle variadischen Argumente müssen Pointer auf die Zielvariablen sein; ein Wert statt eines Pointers führt zum Laufzeitfehler.
Strict innerhalb der Zeile
Übrig gebliebene Tokens vor dem Newline lösen expected newline aus — das Zeilenformat ist verbindlich.
Für zeilenbasierte Parser
Sscanln passt überall dort, wo ein Block aus strukturierter Header-Zeile und freiem Rumpf besteht — Log, Konfig, Protokoll.
Mehrzeilige Eingaben
Folgezeilen werden weder gelesen noch validiert; der Rumpf eines Blocks muss separat ausgewertet werden.
Zu wenige Tokens
Endet die Zeile, bevor alle Pointer befüllt sind, gibt Sscanln die bisher gelesene Anzahl plus einen Fehler zurück.
Alternative ohne Sscanln
strings.SplitN(s, "\n", 2) plus fmt.Sscan auf den ersten Teil ist äquivalent und gibt mehr Kontrolle über den Rest-Puffer.
In Production selten
Im Produktionscode dominiert Sscanf mit explizitem Format-String — Sscanln ist eher in Tools, Skripten und Quick-Parsern zu Hause.