fmt.Fscanln ist die zeilenorientierte Schwester von fmt.Fscan: Sie liest aus einem beliebigen io.Reader — Datei, Netzwerk-Verbindung, os.Stdin, strings.Reader — und stoppt am ersten Newline. Jeder Aufruf konsumiert genau eine Zeile aus dem Stream; was danach kommt, bleibt für den nächsten Aufruf im Puffer. Damit ist Fscanln das passende Werkzeug, wenn ein zeilenweise strukturierter Eingabestrom existiert und jede Zeile dieselbe, feste Anzahl Tokens enthält. Die offizielle Referenz steht auf pkg.go.dev/fmt#Fscanln.

Die Signatur im Detail

fmt.Fscanln folgt dem gleichen Schema wie die anderen Reader-Varianten der Fscan-Familie. Der erste Parameter ist die Eingabequelle, die restlichen sind Pointer auf die Zielvariablen, in welche die geparsten Tokens geschrieben werden.

Go signatur.go
func Fscanln(r io.Reader, a ...any) (n int, err error)

Der Rückgabewert n zählt die erfolgreich gefüllten Argumente, err meldet Format-, Konvertierungs- oder Stream-Fehler. Am Ende des Streams kommt io.EOF zurück — das ist kein Bug, sondern das vereinbarte Ende-Signal.

Pro Aufruf genau eine Zeile

Der entscheidende Unterschied zu Fscan: Fscanln verbraucht den Newline am Zeilenende selbst und positioniert den internen Lesezeiger direkt hinter ihn. Mehrere aufeinanderfolgende Aufrufe lesen damit zuverlässig aufeinanderfolgende Zeilen — ohne dass du selbst Trennzeichen jonglieren musst.

Go zeilen.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	r := strings.NewReader("alice 30\nbob 25\ncarol 41\n")

	var name string
	var age int

	for {
		_, err := fmt.Fscanln(r, &name, &age)
		if err != nil {
			break
		}
		fmt.Printf("%s ist %d Jahre alt\n", name, age)
	}
}
Output
alice ist 30 Jahre alt
bob ist 25 Jahre alt
carol ist 41 Jahre alt

Drei Zeilen Eingabe, drei Iterationen, drei Datensätze — sauber zerlegt ohne manuelles Splitten. Die Schleife terminiert, sobald Fscanln io.EOF liefert.

Extra Tokens auf der Zeile sind ein Fehler

Fscanln ist bewusst streng: Findet sich nach dem letzten erwarteten Argument noch ein weiteres Token vor dem Newline, scheitert der Aufruf. Das schützt vor stillem Datenverlust, ist aber auch der Hauptgrund, warum Fscanln in der Praxis oft zu rigide wirkt.

Go strict.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	r := strings.NewReader("alice 30 extra\n")

	var name string
	var age int

	n, err := fmt.Fscanln(r, &name, &age)
	fmt.Printf("n=%d err=%v\n", n, err)
}
Output
n=2 err=expected newline

Beide Argumente wurden gefüllt (n=2), aber der überzählige Token extra vor dem Newline lässt den Aufruf mit expected newline scheitern. Wer das nicht will, braucht ein toleranteres Verfahren.

Pointer-Pflicht: immer &var

Wie alle Scan-Funktionen schreibt Fscanln direkt in den Speicher der Zielvariablen. Ohne & würde nur eine Kopie befüllt — die Compiler-Prüfung verhindert das schon zur Übersetzungszeit, sobald die Argumente keine Pointer sind.

Go pointer.go
var n int
var s string

fmt.Fscanln(r, &n, &s) // korrekt: Adressen übergeben
// fmt.Fscanln(r, n, s) // Laufzeitfehler: keine Pointer

Fscan vs. Fscanln im Direktvergleich

Beide Funktionen lesen aus demselben io.Reader, behandeln Newlines aber fundamental unterschiedlich. Die Tabelle fasst zusammen, wann welche Variante passt.

AspektFscanFscanln
Newlinewird wie Whitespace behandeltterminiert den Aufruf
Mehrzeilige Eingabeein Aufruf liest über Zeilengrenzen hinwegein Aufruf = eine Zeile
Extra Token vor \nirrelevantFehler expected newline
Typischer Einsatzfreies Token-Schluckenstrukturierte Zeilen mit fester Token-Anzahl
Fehlerbild bei zu wenigen Tokensunexpected newlineunexpected newline

Fscanln ist also der explizite Vertrag „pro Zeile genau diese Felder" — Fscan ist der Streamer.

TSV-Datei zeilenweise parsen

Ein klassischer Einsatzfall: eine tabulatorgetrennte Datei mit fester Spaltenanzahl. Solange jede Zeile garantiert die erwarteten Felder enthält, ist Fscanln die kürzeste sinnvolle Lösung — keine Scanner-Boilerplate, keine manuelle Tokenisierung.

Go tsv.go
package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	data := "alice 87 active\nbob 42 inactive\ncarol 95 active\n"
	r := strings.NewReader(data)

	var name, status string
	var score int

	for {
		_, err := fmt.Fscanln(r, &name, &score, &status)
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("Parse-Fehler:", err)
			break
		}
		fmt.Printf("%-6s score=%d (%s)\n", name, score, status)
	}
}
Output
alice  score=87 (active)
bob    score=42 (inactive)
carol  score=95 (active)

Drei Felder pro Zeile, drei Pointer-Argumente, EOF beendet die Schleife. Genau dafür ist Fscanln gebaut — und wenn die Eingabe wirklich so diszipliniert ist wie hier, gibt es kaum eine kompaktere Variante.

Why-not-Fscanln: Scanner + Fields ist robuster

Sobald die Token-Anzahl pro Zeile variiert oder die Eingabe nicht garantiert wohlgeformt ist, kippt Fscanln ins Negative. Jeder zusätzliche Token erzeugt expected newline; fehlende Tokens erzeugen unexpected newline. In realer Code-Basis ist das schmerzhaft, weil ein einziger Tippfehler im Input die ganze Verarbeitung kippt.

Der idiomatische Go-Weg ist daher: bufio.Scanner liefert die Zeile als String, strings.Fields zerlegt sie in Tokens, und die Konvertierung passiert kontrolliert mit strconv.

Go scanner.go
package main

import (
	"bufio"
	"fmt"
	"strconv"
	"strings"
)

func main() {
	data := "alice 87 active\nbob 42\ncarol 95 active extra\n"
	sc := bufio.NewScanner(strings.NewReader(data))

	for sc.Scan() {
		fields := strings.Fields(sc.Text())
		if len(fields) < 2 {
			fmt.Println("Zeile übersprungen:", sc.Text())
			continue
		}
		name := fields[0]
		score, err := strconv.Atoi(fields[1])
		if err != nil {
			fmt.Println("ungültiger Score:", fields[1])
			continue
		}
		status := "unknown"
		if len(fields) >= 3 {
			status = fields[2]
		}
		fmt.Printf("%-6s score=%d (%s)\n", name, score, status)
	}
}
Output
alice  score=87 (active)
bob    score=42 (unknown)
carol  score=95 (active)

Dieselbe Eingabe — aber statt zweimal in einen Fehler zu laufen, fängt die Schleife die unvollständige Bob-Zeile mit einem Default-Status ab und ignoriert das überzählige extra bei Carol. Für produktive Code-Pfade ist das der robustere Default; Fscanln bleibt elegant für sauber definierte interne Formate.

Reader-Quelle mit Newline-Stopp

Fscanln liest aus jedem io.Reader — Datei, Netzverbindung, strings.Reader, os.Stdin — und stoppt deterministisch am ersten Newline.

Pro Aufruf genau eine Zeile

Mehrere Fscanln-Aufrufe konsumieren aufeinanderfolgende Zeilen; der Newline selbst wird mit verbraucht, der Lesezeiger steht danach am Anfang der nächsten Zeile.

Strict-Mode: extra Tokens = Fehler

Findet sich nach den erwarteten Argumenten noch ein Token vor dem Newline, scheitert der Aufruf mit expected newline — selbst wenn alle Pointer befüllt wurden.

Pointer-Pflicht

Argumente müssen Adressen sein, also &name, &score. Ohne & schlägt der Aufruf zur Laufzeit fehl, weil Fscanln direkt in den Speicher schreibt.

bufio.Scanner ist robuster

Bei variabler Token-Anzahl oder unsicherer Eingabe ist bufio.Scanner.Text() + strings.Fields + strconv der idiomatische Weg — toleranter, debugbarer, ohne strikte Newline-Falle.

EOF ist kein Fehler

io.EOF signalisiert das saubere Ende des Streams. In Schleifen explizit gegen io.EOF prüfen und die Iteration beenden, statt EOF wie einen Parse-Fehler zu behandeln.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht