strings.TrimSuffix entfernt einen ganzen Substring vom Ende eines Strings — aber nur dann, wenn der String tatsächlich mit diesem Suffix endet. Trifft das Suffix nicht zu, wird der Originalstring unverändert zurückgegeben. Die Funktion ist der direkte Spiegel zu TrimPrefix und arbeitet semantisch völlig anders als TrimRight, das jedes Zeichen aus einem Cutset interpretiert. Genau diese Unterscheidung ist im Alltag das häufigste Stolperthema: Wer eine Dateiendung wie .go entfernen möchte, will praktisch immer TrimSuffix und nicht TrimRight.

Die Signatur ist minimal und drückt das Konzept präzise aus: Zwei Strings hinein, ein String hinaus. Das zweite Argument wird als zusammenhängende Sequenz interpretiert, nicht als Zeichenmenge.

Go signatur.go
func TrimSuffix(s, suffix string) string

Intern prüft Go schlicht via HasSuffix, ob s mit suffix endet — falls ja, wird s[:len(s)-len(suffix)] zurückgegeben, sonst s unverändert. Das bedeutet auch: Es gibt keine Allokation, der Rückgabewert ist ein Slice in den ursprünglichen Strings-Speicher.

Der zentrale konzeptionelle Unterschied zu TrimRight: Das zweite Argument ist ein zusammenhängender String, nicht eine Menge einzelner Zeichen. TrimRight("hello.go", ".go") würde solange Zeichen entfernen, wie diese in der Menge {., g, o} liegen — und damit aus hello.go ein hell machen, weil das o am Schluss von hello ebenfalls im Cutset enthalten ist.

Go vs_trimright.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "hello.go"

	fmt.Printf("TrimSuffix: %q\n", strings.TrimSuffix(s, ".go"))
	fmt.Printf("TrimRight:  %q\n", strings.TrimRight(s, ".go"))
}
Output
TrimSuffix: "hello"
TrimRight:  "hell"

Diese eine Zeile Output entscheidet in der Praxis sehr oft, ob ein Tool korrekt funktioniert oder Daten zerstört. Für jede Form von Dateiendungs-, Marker- oder Token-Entfernung ist TrimSuffix die richtige Wahl, niemals TrimRight.

Kommt das Suffix mehrfach hintereinander am Ende vor, wird nur eine einzige Instanz entfernt — die letzte. Die Funktion ist nicht iterativ; sie führt genau einen Schnitt durch.

Go nur_einmal.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "log.txt.txt"

	fmt.Printf("einmal: %q\n", strings.TrimSuffix(s, ".txt"))

	// Für mehrfache Entfernung explizit schleifen
	for strings.HasSuffix(s, ".txt") {
		s = strings.TrimSuffix(s, ".txt")
	}
	fmt.Printf("Schleife: %q\n", s)
}
Output
einmal: "log.txt"
Schleife: "log"

Wenn wiederholtes Entfernen gewünscht ist, muss man die Schleife selbst formulieren. Das ist bewusst so gehalten: TrimSuffix bleibt eine vorhersehbare O(1)-Operation auf String-Ebene.

Ein leerer Suffix-String ist ein gültiger Eingabewert. Da jeder String per Definition mit dem Leerstring endet, müsste man rein logisch nichts entfernen — und genau das tut die Funktion.

Go leeres_suffix.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Printf("%q\n", strings.TrimSuffix("hallo", ""))
	fmt.Printf("%q\n", strings.TrimSuffix("", ""))
}
Output
"hallo"
""

Dieses Verhalten ist nützlich, wenn das Suffix dynamisch bestimmt wird und gelegentlich leer sein kann — man braucht keine Sonderfallbehandlung.

Go bietet drei eng verwandte Funktionen rund um Suffixe, die unterschiedliche Bedürfnisse abdecken. Welche passt, hängt davon ab, ob man nur prüfen, entfernen oder beides in einem Schritt erledigen will.

FunktionRückgabeWann nutzen?
HasSuffix(s, suf)boolNur prüfen, ob String endet wie erwartet
TrimSuffix(s, suf)string (entfernt oder original)Entfernen falls vorhanden, sonst Original
CutSuffix(s, suf)(string, bool)Entfernen + Treffer-Information in einem Aufruf

CutSuffix ist seit Go 1.20 verfügbar und kombiniert beide Schritte. Wer den Kontroll-Flag braucht (z.B. zum Verzweigen, falls das Suffix fehlt), greift heute meist zu CutSuffix. Für reine „abschneiden falls da" ist TrimSuffix weiterhin der klare und ältere Klassiker.

TrimSuffix ist allokationsfrei. Bei einem Treffer wird ein Sub-Slice des ursprünglichen String-Headers zurückgegeben — kein Kopieren von Bytes, kein Garbage. Bei einem Miss wird derselbe String-Header unverändert zurückgereicht, also ist die Operation effektiv ein HasSuffix-Check.

Go performance.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "report.csv"

	// Treffer: Sub-Slice
	a := strings.TrimSuffix(s, ".csv")
	// Kein Treffer: identischer Header
	b := strings.TrimSuffix(s, ".xml")

	fmt.Printf("Treffer:    %q\n", a)
	fmt.Printf("Kein Match: %q (== Original: %v)\n", b, b == s)
}
Output
Treffer:    "report"
Kein Match: "report.csv" (== Original: true)

Damit eignet sich TrimSuffix auch für Hot-Path-Code wie Log-Parsing oder Pfad-Normalisierung, ohne dass man sich um Allokationsdruck Gedanken machen muss.

Der klassische Einsatzfall ist das Reduzieren eines Dateinamens auf seinen Basisnamen. Im Gegensatz zu filepath.Ext plus manueller Längenrechnung ist die Variante mit TrimSuffix direkt lesbar.

Go basename.go
package main

import (
	"fmt"
	"strings"
)

func basename(name, ext string) string {
	return strings.TrimSuffix(name, ext)
}

func main() {
	for _, file := range []string{"foto.jpg", "urlaub.jpg", "readme.md"} {
		fmt.Printf("%-12s -> %s\n", file, basename(file, ".jpg"))
	}
}
Output
foto.jpg     -> foto
urlaub.jpg   -> urlaub
readme.md    -> readme.md

Der Vorteil zeigt sich an readme.md: Da die Endung nicht passt, bleibt der Name unangetastet. Genau dieses defensive Verhalten ist beim Verarbeiten gemischter Listen Gold wert.

Eine zweite häufige Anwendung ist das Entfernen von Marker-Suffixen aus Bezeichnern — etwa _test aus Go-Testdatei-Namen für eine Anzeige, oder _v2/_legacy aus Versionsmarkern. Das Muster ist immer dasselbe: kombiniere Endungen erst weg, dann den Marker.

Go display_name.go
package main

import (
	"fmt"
	"strings"
)

func displayName(file string) string {
	name := strings.TrimSuffix(file, ".go")
	name = strings.TrimSuffix(name, "_test")
	return name
}

func main() {
	files := []string{"user_test.go", "handler.go", "auth_test.go", "main.go"}
	for _, f := range files {
		fmt.Printf("%-16s -> %s\n", f, displayName(f))
	}
}
Output
user_test.go     -> user
handler.go       -> handler
auth_test.go     -> auth
main.go          -> main

Die Verkettung mehrerer TrimSuffix-Aufrufe ergibt eine kleine Pipeline, in der jeder Schritt nur dann zuschneidet, wenn er sein Suffix tatsächlich findet. So entstehen normalisierte Anzeigenamen ohne komplizierte Regex.

Substring statt Cutset

TrimSuffix behandelt sein zweites Argument als zusammenhängenden String — nicht als Zeichenmenge wie TrimRight.

Häufigste Verwechslung: TrimRight

TrimRight("hello.go", ".go") ergibt hell, weil o im Cutset steckt — bei Dateiendungen fast immer ein Bug.

Nur eine Instanz wird entfernt

Mehrfach hintereinander angehängte Suffixe verlangen eine eigene for-Schleife mit HasSuffix-Bedingung.

Leeres Suffix lässt String unverändert

TrimSuffix(s, "") gibt s zurück — keine Sonderfallbehandlung nötig, wenn das Suffix dynamisch entsteht.

Ohne Match: Original zurück

Passt das Suffix nicht, kommt derselbe String-Header zurück — result == s ist dann wahr.

CutSuffix als modernerer Ersatz

Seit Go 1.20 liefert CutSuffix zusätzlich ein bool — praktisch, wenn man auf den Treffer reagieren will.

Allokationsfrei dank Slicing

Rückgabewert ist ein Sub-Slice des Originals, daher ohne Heap-Druck auch im Hot-Path verwendbar.

Threadsafe durch String-Immutabilität

Strings sind in Go unveränderlich; TrimSuffix ist damit ohne Synchronisation parallel nutzbar.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das strings-Paket — String-Manipulation

Zur Übersicht