strings.TrimLeft läuft vom Anfang eines Strings vorwärts und entfernt solange Zeichen, wie sie im übergebenen cutset enthalten sind. Sobald die erste Rune auftaucht, die nicht zum Cutset gehört, stoppt das Trimmen und der Rest des Strings bleibt unverändert.
Entscheidend ist die gleiche Falle wie bei Trim: der zweite Parameter ist keine Sequenz, sondern eine Menge einzelner Runes. TrimLeft(s, "file") entfernt also keinen Präfix "file", sondern jede beliebige Folge der Buchstaben f, i, l und e vom linken Rand — für Substring-Vergleich braucht es TrimPrefix.
Die Funktion ist denkbar schlank — zwei String-Parameter, ein neuer String als Ergebnis. Der erste Parameter ist die Eingabe, der zweite die Menge der zu entfernenden Runes.
func TrimLeft(s, cutset string) stringWichtig zu wissen: cutset wird intern in eine Rune-Menge übersetzt. Reihenfolge und Wiederholungen darin spielen keine Rolle — "abc", "cba" und "aabbcc" verhalten sich identisch.
Wer einen Präfix-String abschneiden möchte, greift instinktiv zu TrimLeft — und produziert damit Bugs, die erst mit echten Daten auffallen. Das folgende Beispiel zeigt den Unterschied schwarz auf weiß: TrimLeft frisst sich durch alle f/i/l/e-Runen am Anfang, TrimPrefix prüft hingegen exakt auf die Zeichenkette "file".
package main
import (
"fmt"
"strings"
)
func main() {
s := "effective.go"
fmt.Printf("TrimLeft : %q\n", strings.TrimLeft(s, "file"))
fmt.Printf("TrimPrefix : %q\n", strings.TrimPrefix(s, "file"))
}TrimLeft : "ctive.go"
TrimPrefix : "effective.go"TrimLeft sieht in "effective.go" die Runen e, f, f, e — alle im Cutset {f, i, l, e} — und entfernt sie, bis das c auftaucht, das nicht im Cutset steht. TrimPrefix dagegen vergleicht die ersten vier Zeichen mit dem Substring "file", findet keinen Match und gibt den String unverändert zurück.
TrimLeft arbeitet nicht zeichenweise mit Stopp nach einem Treffer, sondern läuft solange weiter, wie das aktuelle Zeichen im Cutset enthalten ist. Das macht es ideal für „beliebige Anzahl von führenden X".
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%q\n", strings.TrimLeft("xxxhello", "x"))
fmt.Printf("%q\n", strings.TrimLeft("---ok", "-"))
fmt.Printf("%q\n", strings.TrimLeft("abcabcZ", "abc"))
}"hello"
"ok"
"Z"Im letzten Fall ist cutset = {a, b, c}, und der gesamte Anfang besteht aus diesen drei Runen in beliebiger Mischung — alles fliegt raus, bis das Z auftaucht.
Der Cutset wird als UTF-8 dekodiert, nicht als Byte-Folge interpretiert. Deshalb funktionieren auch deutsche Umlaute, Emojis oder kyrillische Buchstaben ohne Sonderbehandlung — eine ä zählt als eine Rune, nicht als zwei Bytes.
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%q\n", strings.TrimLeft("äääabc", "ä"))
fmt.Printf("%q\n", strings.TrimLeft("😀😀hi", "😀"))
fmt.Printf("%q\n", strings.TrimLeft("ööäabc", "äö"))
}"abc"
"hi"
"abc"Würde Go den Cutset byteweise vergleichen, käme bei "äääabc" Müll heraus, weil ä aus zwei Bytes (0xC3 0xA4) besteht. Stattdessen sieht die Funktion sauber drei ä-Runen am Anfang und entfernt sie.
Falls am Anfang kein einziges Cutset-Zeichen steht, gibt TrimLeft den ursprünglichen String unverändert zurück. Das ist nicht nur semantisch sauber, sondern auch effizient: intern wird derselbe String-Header (Pointer + Länge) zurückgegeben, ohne neue Allokation.
package main
import (
"fmt"
"strings"
)
func main() {
s := "hello world"
out := strings.TrimLeft(s, "xyz")
fmt.Printf("gleich: %v\n", s == out)
fmt.Printf("daten: %q\n", out)
}gleich: true
daten: "hello world"Auch der umgekehrte Fall cutset == "" führt zu identischer Rückgabe — eine leere Menge matcht nichts, also wird nichts entfernt.
Drei Funktionen, drei klar getrennte Aufgaben. Die Tabelle fasst die wichtigsten Unterschiede zusammen — wer sie verinnerlicht, vermeidet 90 % der typischen Bugs in diesem Eck der stdlib.
| Funktion | Zweiter Parameter | Wirkung | Beispiel ("filefile", "file") |
|---|---|---|---|
TrimLeft | Cutset (Rune-Menge) | Entfernt links solange Match | "" (alles f/i/l/e am Anfang) |
Trim | Cutset (Rune-Menge) | Entfernt links und rechts | "" |
TrimPrefix | Substring | Entfernt genau diesen Präfix (1x) | "file" (1x "file" weg) |
TrimRight | Cutset (Rune-Menge) | Entfernt nur rechts | "" |
TrimLeftFunc | Prädikat-Funktion | Entfernt links solange f(r) == true | je nach Funktion |
Ein klassischer Anwendungsfall: numerische IDs, die mit führenden Nullen ankommen, sollen normalisiert werden. TrimLeft mit Cutset "0" erledigt das in einer Zeile — egal wie viele Nullen vorne stehen.
package main
import (
"fmt"
"strings"
)
func main() {
ids := []string{"0042", "007", "00000123", "0000"}
for _, id := range ids {
clean := strings.TrimLeft(id, "0")
fmt.Printf("%-10s -> %q\n", id, clean)
}
}0042 -> "42"
007 -> "7"
00000123 -> "123"
0000 -> ""Achtung beim Edge Case "0000": das Ergebnis ist der leere String, weil alle Zeichen aus dem Cutset stammen. Wer stattdessen "0" zurückhaben möchte (die Zahl Null), muss explizit auf das leere Resultat prüfen und einen Default setzen.
Beim Parsen von indentierten Zeilen — etwa für YAML-Light, Markdown-Listen oder ASCII-Tabellen — braucht es oft nur das linke Trimmen, weil rechts ein bedeutsames Newline oder Trailing-Token stehen kann. TrimLeft(s, " \t") liefert genau das.
package main
import (
"fmt"
"strings"
)
func main() {
lines := []string{
" - apfel",
"\t\t- birne",
" - kirsche ",
}
for _, l := range lines {
body := strings.TrimLeft(l, " \t")
indent := len(l) - len(body)
fmt.Printf("indent=%d body=%q\n", indent, body)
}
}indent=2 body="- apfel"
indent=2 body="- birne"
indent=4 body="- kirsche "TrimSpace wäre hier falsch, weil es auch rechts kürzt — die zwei Leerzeichen hinter "kirsche" würden verschwinden, was bei manchen Formaten (z. B. Markdown-Hard-Break) semantisch relevant ist. TrimLeft mit explizitem Whitespace-Cutset gibt die volle Kontrolle.
cutset ist eine Rune-Menge, kein Substring
Der zweite Parameter wird intern zu einer Menge einzelner Runes — Reihenfolge und Wiederholungen sind egal. TrimLeft(s, "abc") entfernt jede beliebige Folge aus a, b, c.
Häufigste Verwechslung: TrimPrefix
Wer einen Präfix-String abschneiden will, braucht TrimPrefix. TrimLeft("effective.go", "file") liefert "ctive.go", nicht "effective.go".
Mehrfach-Matching ist eingebaut
TrimLeft läuft so lange weiter, wie das aktuelle Zeichen im Cutset steht — "xxxhello" mit Cutset "x" ergibt "hello", nicht "xxhello".
Rune-aware, UTF-8-sicher
Multi-Byte-Runen wie ä oder Emojis werden korrekt als eine Einheit behandelt — keine kaputten Byte-Schnitte mitten in einer Rune.
Leerer Cutset = unveränderter String
TrimLeft(s, "") gibt s zurück — eine leere Menge matcht nichts, also wird nichts entfernt.
Ohne Trim keine Allokation
Wenn am Anfang kein Cutset-Zeichen steht, gibt TrimLeft denselben String-Header zurück — kein Kopieren, kein neuer Heap-Eintrag.
Für Substring-Präfix: TrimPrefix
Sobald die Semantik „entferne genau diese Zeichenfolge vorn" gemeint ist, ist TrimPrefix die richtige Wahl — TrimLeft ist ausschließlich für Rune-Mengen gedacht.
Threadsafe und ohne Seiteneffekte
Strings sind in Go immutable, TrimLeft mutiert nichts und kann parallel auf demselben Input aufgerufen werden.