strings.LastIndexAny liefert den Byte-Offset des letzten Zeichens aus einem Cutset chars, das in s vorkommt. Die Funktion arbeitet rune-aware: chars wird als Menge von Unicode-Codepoints interpretiert, nicht als Bytefolge. Damit lassen sich rechtsbündige Trennzeichen-Suchen mit mehreren Kandidaten in einem einzigen Aufruf erledigen — typischerweise dann, wenn das relevante Trennzeichen näher am Ende der Eingabe erwartet wird (Dateinamen, Pfade, Versions-Strings). Bei keinem Treffer oder leerem Cutset ist der Rückgabewert -1.
Die Signatur ist symmetrisch zu IndexAny, dreht aber die Suchrichtung um. Es gibt keinen Startindex-Parameter — die Suche beginnt immer am rechten Stringende und wandert rückwärts zur ersten gefundenen Rune.
func LastIndexAny(s, chars string) intDer zurückgegebene int ist ein Byte-Offset in s, kein Rune-Index. Das ist wichtig, sobald s Multi-Byte-Sequenzen enthält, denn dann fallen Rune-Position und Byte-Offset auseinander.
LastIndexAny scannt s von hinten nach vorne und gibt den Offset der ersten Rune zurück, deren Codepoint im Cutset enthalten ist. Im Vergleich zu IndexAny zeigt sich der Unterschied am gleichen Eingabestring sofort: der erste Treffer von links ist nicht dasselbe wie der letzte von rechts.
package main
import (
"fmt"
"strings"
)
func main() {
s := "user@host.example.com"
fmt.Println(strings.IndexAny(s, "@.")) // erstes @ oder .
fmt.Println(strings.LastIndexAny(s, "@.")) // letztes @ oder .
}4
16Hier trifft IndexAny das @ an Position 4, während LastIndexAny den letzten Punkt vor com an Byte-Offset 16 findet. Der Cutset bleibt identisch — nur die Richtung entscheidet, welche Rune zurückgemeldet wird.
Findet LastIndexAny eine Rune, die in UTF-8 aus mehreren Bytes besteht, zeigt der Rückgabewert auf das erste Byte dieser Sequenz. Damit lässt sich der Treffer direkt als Slice-Grenze verwenden, ohne zusätzliche Korrektur.
package main
import (
"fmt"
"strings"
)
func main() {
s := "café — bäckerei"
idx := strings.LastIndexAny(s, "äé")
fmt.Println(idx)
fmt.Println(s[idx:])
}11
äckereiDas ä belegt zwei Bytes (0xC3 0xA4), der Offset 11 markiert dessen erstes Byte. s[idx:] liefert deshalb einen syntaktisch korrekten UTF-8-Substring ab dem Treffer und nicht etwa ein zerbrochenes Surrogat.
Ein leerer Cutset enthält keine Codepoints, also kann nichts gematcht werden — das Ergebnis ist -1. Das gleiche gilt für ein leeres s, weil es nichts zum Durchsuchen gibt.
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.LastIndexAny("abc", ""))
fmt.Println(strings.LastIndexAny("", "abc"))
fmt.Println(strings.LastIndexAny("", ""))
}-1
-1
-1Damit verhält sich LastIndexAny konsistent zu IndexAny und braucht keinen Spezialfall im aufrufenden Code: ein -1-Check deckt alle Leer-Varianten ab.
Die drei Funktionen wirken auf den ersten Blick verwandt, lösen aber jeweils ein anderes Problem. Die folgende Tabelle stellt Richtung und Treffer-Definition gegenüber.
| Funktion | Sucht nach | Richtung | Rückgabe |
|---|---|---|---|
IndexAny(s, chars) | beliebige Rune aus chars | von links | Byte-Offset des ersten Treffers / -1 |
LastIndexAny(s, chars) | beliebige Rune aus chars | von rechts | Byte-Offset des letzten Treffers / -1 |
LastIndex(s, substr) | feste Bytefolge substr | von rechts | Byte-Offset des letzten Vorkommens / -1 |
LastIndex arbeitet auf Substring-Ebene, LastIndexAny auf Einzel-Rune-Ebene mit Cutset-Logik. Wer mehrere mögliche Trennzeichen rechtsbündig sucht, spart sich mit LastIndexAny eine Schleife aus LastIndex-Aufrufen samt Maximum-Berechnung.
Plattformübergreifende Pfad-Parser müssen sowohl / als auch \ als Trennzeichen akzeptieren. Mit LastIndexAny findet man das letzte Trennzeichen in einem Aufruf und kann den Verzeichnisteil sauber abschneiden.
package main
import (
"fmt"
"strings"
)
func dirname(p string) string {
i := strings.LastIndexAny(p, `/\`)
if i < 0 {
return "."
}
return p[:i]
}
func main() {
fmt.Println(dirname("/etc/nginx/nginx.conf"))
fmt.Println(dirname(`C:\Users\michael\notes.md`))
fmt.Println(dirname("plain.txt"))
}/etc/nginx
C:\Users\michael
.Der Rohstring `/\` enthält genau die beiden Trennzeichen ohne Escape-Aufwand. Fällt der Treffer weg, signalisiert i < 0, dass kein Verzeichnis-Teil existiert, und die Funktion liefert den POSIX-üblichen .-Wert zurück.
Bei Versions-Strings wie app-1.2.3 oder lib.2024-11 ist offen, ob das letzte semantische Trennzeichen ein . oder ein - ist. LastIndexAny ermittelt den jeweils rechten Trenner und teilt den String an genau dieser Stelle.
package main
import (
"fmt"
"strings"
)
func splitSuffix(s string) (head, tail string) {
i := strings.LastIndexAny(s, ".-")
if i < 0 {
return s, ""
}
return s[:i], s[i+1:]
}
func main() {
for _, v := range []string{"app-1.2.3", "lib.2024-11", "kernel", "v1.0-rc1"} {
h, t := splitSuffix(v)
fmt.Printf("%-12s -> head=%q tail=%q\n", v, h, t)
}
}app-1.2.3 -> head="app-1.2" tail="3"
lib.2024-11 -> head="lib.2024" tail="11"
kernel -> head="kernel" tail=""
v1.0-rc1 -> head="v1.0" tail="rc1"Beide Trennzeichen liegen gleichberechtigt im Cutset; allein die Position im String entscheidet, welches gewinnt. Soll dagegen . Vorrang vor - haben, müsste man zuerst LastIndex(s, ".") versuchen und nur bei Fehlschlag auf - ausweichen — LastIndexAny selbst kennt keine Priorität innerhalb seines Cutsets.
Cutset ist rune-aware
chars wird als Menge von Unicode-Codepoints gelesen, nicht als Bytemenge — Multi-Byte-Runen funktionieren ohne Sondercode.
Rückgabe ist ein Byte-Offset
Der int zeigt auf ein Byte in s, nicht auf einen Rune-Index; bei UTF-8-Sequenzen auf das erste Byte der Rune.
-1 bei Nicht-Treffer
Findet sich keine Rune aus chars in s, ist das Resultat -1 — ein einziger Check deckt alle Fehlpfade ab.
Leerer Cutset ergibt -1
LastIndexAny(s, "") matcht nichts und liefert immer -1, unabhängig von s.
Leeres s ergibt -1
Ohne Inhalt gibt es nichts zu durchsuchen — der Aufruf endet sofort mit -1.
Threadsafe auf Strings
Strings sind in Go immutabel; parallele Aufrufe auf demselben s sind ohne Sperren erlaubt.
Ersetzt mehrere LastIndex-Calls
Ein LastIndexAny-Aufruf mit Cutset spart die Schleife aus mehreren LastIndex-Aufrufen plus Maximum-Berechnung.
Schneller als regex
Für reine Zeichenmengen ohne Quantoren ist LastIndexAny deutlich günstiger als regexp — keine Engine-Initialisierung, kein Backtracking.