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.
func TrimSuffix(s, suffix string) stringIntern 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.
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"))
}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.
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)
}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.
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%q\n", strings.TrimSuffix("hallo", ""))
fmt.Printf("%q\n", strings.TrimSuffix("", ""))
}"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.
| Funktion | Rückgabe | Wann nutzen? |
|---|---|---|
HasSuffix(s, suf) | bool | Nur 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.
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)
}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.
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"))
}
}foto.jpg -> foto
urlaub.jpg -> urlaub
readme.md -> readme.mdDer 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.
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))
}
}user_test.go -> user
handler.go -> handler
auth_test.go -> auth
main.go -> mainDie 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
strings.TrimSuffixstrings.CutSuffix(Go 1.20+)