mitmario.dev

Anker, Gruppen und Alternativen

JavaScript Im Browser 4 Min Lesezeit 4 BeispieleLektion 3 von 6

Bis hierhin passt ein Muster irgendwo im Text. Diese Lektion macht es fest, gruppiert Teile davon und holt sie wieder heraus. Damit ist der Werkzeugkasten für Formularprüfungen komplett.

Anfang und Ende festmachen

Ohne Anker passt es irgendwo
const ohne = /\d{5}/;
const mit = /^\d{5}$/;

// Bei der richtigen Eingabe sind beide einig.
console.log("04103:", ohne.test("04103"), mit.test("04103"));

// Bei einer längeren nicht mehr.
console.log("041030:", ohne.test("041030"), mit.test("041030"));

// Und bei einer, in der die Zahl nur irgendwo steht,
// erst recht nicht.
console.log("PLZ 04103 Leipzig:", ohne.test("PLZ 04103 Leipzig"));
console.log("mit Ankern:", mit.test("PLZ 04103 Leipzig"));

// ^ heißt Anfang des Textes, $ heißt Ende.
console.log("nur Anfang:", /^Kurs/.test("Kurs 3"), /^Kurs/.test("Ein Kurs"));
console.log("nur Ende:", /Kurs$/.test("Ein Kurs"), /Kurs$/.test("Kurs 3"));

^ steht für den Anfang des Textes, $ für das Ende. Ein Muster zwischen beiden muss den ganzen Text beschreiben und nicht nur ein Stück daraus.

Das ist der häufigste Fehler bei selbstgebauten Prüfmustern. /\d{5}/ sieht aus wie „eine Postleitzahl” und heißt in Wirklichkeit „irgendwo fünf Ziffern hintereinander”. Damit kommt 041030 durch, und PLZ 04103 Leipzig auch. Die Prüfung wirkt streng und ist es nicht.

Zwei Zeilen in der Eingabezeile der Console zeigen dir das, und du solltest sie wirklich tippen: /\d{5}/.test("PLZ 04103 Leipzig") antwortet true, /^\d{5}$/.test("PLZ 04103 Leipzig") antwortet false. Ein Muster, das eine ganze Adresse für eine Postleitzahl hält, sieht im Code genauso harmlos aus wie eins, das es richtig macht.

Die Faustregel dazu ist kurz: beim Suchen ohne Anker, beim Prüfen mit. Wenn du wissen willst, ob etwas vorkommt, willst du keine Anker. Wenn du wissen willst, ob eine Eingabe die richtige Form hat, brauchst du beide.

Einzeln haben sie auch ihren Zweck. /^Kurs/ prüft den Anfang, /Kurs$/ das Ende, und beides ist dasselbe wie startsWith und endsWith aus Lektion 8.2, nur eben mit einer Form statt einem festen Text.

Klammern fassen zusammen

Runde Klammern fassen zusammen
// Ohne Klammer bezieht sich die Mengenangabe nur auf das
// Zeichen davor. Hier also nur auf das a.
console.log("ohne Klammer:", /^ab+$/.test("abab"), /^ab+$/.test("abbb"));

// Mit Klammer bezieht sie sich auf die ganze Gruppe.
console.log("mit Klammer:", /^(ab)+$/.test("abab"), /^(ab)+$/.test("abbb"));

// Der senkrechte Strich heißt oder. Eine Klammer grenzt ein,
// wie weit das oder reicht.
console.log("ohne Klammer:", /^Kurs|Buch$/.test("Kurs xyz"));
console.log("mit Klammer:", /^(Kurs|Buch)$/.test("Kurs xyz"));
console.log("Treffer:", /^(Kurs|Buch)$/.test("Buch"));

// Damit lässt sich die Stunde aus 9.2 richtig eingrenzen:
// 00 bis 19 oder 20 bis 23.
const uhr = /^([01]\d|2[0-3]):[0-5]\d$/;
console.log("Uhrzeit:", uhr.test("09:45"), uhr.test("23:59"), uhr.test("24:00"));

Eine Mengenangabe bezieht sich immer nur auf das eine Zeichen davor. ab+ heißt also „ein a, dann ein oder mehr b”. Wer „ab, ein oder mehrmals” meint, braucht eine Klammer: (ab)+.

Der senkrechte Strich heißt „oder”, und er reicht weiter, als man denkt: ^Kurs|Buch$ ist nicht „genau Kurs oder genau Buch”, sondern „am Anfang Kurs” oder „am Ende Buch”. Eine Klammer grenzt ihn ein, und ^(Kurs|Buch)$ ist das, was gemeint war.

Damit lässt sich die Uhrzeit aus Lektion 9.2 richtig eingrenzen. Eine gültige Stunde ist entweder 0 oder 1 mit einer beliebigen Ziffer dahinter, oder eine 2 mit einer Ziffer von 0 bis 3. Als Muster: ([01]\d|2[0-3]). So etwas schreibt man nicht aus dem Kopf, man baut es Stück für Stück und probiert es aus, und zwar in der Eingabezeile: erst /^[01]\d$/ an ein paar Stunden halten, dann den zweiten Zweig dazu, dann die Minuten. Wer das Muster in einem Stück in den Editor schreibt und dann rätselt, warum 25:00 durchkommt, macht sich die Arbeit schwerer, als sie ist.

Und sie merken sich, was sie getroffen haben

Gruppen merken sich ihren Treffer
const datum = "2026-08-03";

// match liefert bei einem Treffer eine Liste: an Stelle 0 der
// ganze Treffer, danach je eine Gruppe in ihrer Reihenfolge.
const treffer = datum.match(/^(\d{4})-(\d{2})-(\d{2})$/);
console.log("ganz:", treffer[0]);
console.log("Jahr:", treffer[1]);
console.log("Monat:", treffer[2]);
console.log("Tag:", treffer[3]);

// Nach Zahlen zu greifen ist unleserlich. Benannte Gruppen
// legen dieselben Werte unter Namen ab.
const benannt = datum.match(/^(?<jahr>\d{4})-(?<monat>\d{2})-(?<tag>\d{2})$/);
console.log("groups:", benannt.groups);
console.log("Jahr benannt:", benannt.groups.jahr);

// Eine Klammer, die nur zusammenfassen soll, schreibt man mit
// ?: davor. Dann merkt sie sich nichts.
const ohneMerken = "abab".match(/^(?:ab)+$/);
console.log("ohne Merken:", ohneMerken.length);

Eine runde Klammer tut noch etwas Zweites: sie merkt sich, welcher Teil des Textes auf sie gepasst hat. Damit wird aus einer Prüfung ein Zerlegen.

match liefert bei einem Treffer eine Liste. An Stelle 0 steht der ganze Treffer, dahinter je eine Gruppe in der Reihenfolge ihrer öffnenden Klammern. treffer[1] ist also die erste Gruppe, nicht die zweite.

Nach Zahlen zu greifen ist unleserlich, besonders wenn später eine Gruppe dazukommt und alles verrutscht. Deshalb gibt es benannte Gruppen: (?<jahr>\d{4}) legt den Treffer unter groups.jahr ab. Das ist etwas mehr zu tippen und in jedem Fall die bessere Wahl.

Wenn du eine Klammer nur zum Zusammenfassen brauchst und nichts merken willst, schreibst du (?:...). Das ist eine Kleinigkeit, aber sie hält die Nummerierung sauber.

Wörter statt Textteile

Die Wortgrenze
// Ohne \b trifft Kurs auch mitten in einem längeren Wort.
console.log("ohne \\b:", /Kurs/.test("Kursaal"), /Kurs/.test("Ein Kurs"));

// \b markiert die Stelle zwischen einem Wortzeichen und
// einem anderen Zeichen, also den Rand eines Wortes.
console.log("mit \\b:", /\bKurs\b/.test("Kursaal"), /\bKurs\b/.test("Ein Kurs"));

// Auch am Satzende und vor einem Satzzeichen.
console.log("Satzende:", /\bKurs\b/.test("Das ist ein Kurs."));

// Der Unterschied zu den Ankern: ^ und $ meinen den ganzen
// Text, \b meint jedes Wort darin.
console.log("Anker:", /^Kurs$/.test("Ein Kurs"));
console.log("Wortgrenze:", /\bKurs\b/.test("Ein Kurs"));

// Achtung: \b kennt nur Wortzeichen im Sinn von \w. Ein Umlaut
// ist keines, also sitzt mitten in Öse eine Wortgrenze.
console.log("mitten im Wort:", /\bse\b/.test("Öse"));

// Und ein Wort, das mit einem Umlaut beginnt, hat davor keine.
console.log("ganzes Wort:", /\bÖse\b/.test("Eine Öse hier"));

\b steht für eine Wortgrenze, also für die Stelle zwischen einem Wortzeichen und etwas anderem. Es passt auf kein Zeichen, es beschreibt nur eine Position.

Der Nutzen ist schnell erklärt: /Kurs/ trifft auch in Kursaal, /\bKurs\b/ nicht. Für alles, was nach einem Wort sucht statt nach einer Zeichenfolge, gehört es dazu, und beim Ersetzen erst recht.

Der Unterschied zu den Ankern: ^ und $ meinen den ganzen Text, \b meint jedes Wort darin. In einem Satz mit zehn Wörtern gibt es zwanzig Wortgrenzen.

Und weil es auf \w aufbaut, hat es dieselbe Umlaut-Schwäche wie in Lektion 9.2. Ein Ö ist für \b kein Wortzeichen, also sitzt mitten in „Öse” eine Wortgrenze und vor dem Wort keine. Beide Effekte stehen am Ende des Beispiels, und beide gehen in die falsche Richtung: \bse\b trifft, \bÖse\b nicht. Bei deutschen Texten lohnt sich deshalb ein Versuch mit einem Umlaut, bevor man sich darauf verlässt.

Zum Mitnehmen

Ein Muster ohne Anker passt irgendwo im Text. Für eine Suche ist das richtig, für eine Prüfung fast immer falsch, und das ist der häufigste Fehler bei selbstgebauten Mustern.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.