Ihr Regex ist anfällig für das Verhalten von O (N ^ 2) des Regex-Moduls "Backtracking", das in Vim (und vielen anderen Sprachen und Umgebungen) verwendet wird.
Glücklicherweise gibt es Möglichkeiten, äquivalente Ausdrücke zu schreiben, die kein übermäßiges Backtracking verursachen. Zum Beispiel:
/^\([^|]*|\)\.*$
Im Allgemeinen müssen Sie nicht auf "acht oder mehr" passen, denn wenn Sie bereits wissen, ist die Zeile problematisch, wenn sie acht hat (ob sie mehr hat oder nicht).
Wenn Sie tatsächlich die gesamte Zeile anpassen müssen (z. B. weil sie Teil einer :s
Operation ist), müssen Sie den letzten Teil ( .*$
) beibehalten. Wenn Sie nur den Regex verwenden, um die "acht oder mehr" Zeilen zu finden, können .*$
Sie das Ende beenden.
Außerdem empfehle ich, nur eine Seite der Pipe innerhalb der Gruppe zu suchen, die Sie wiederholen. Dies vereinfacht sowohl das Nachdenken darüber, wie der Regex mit den Zeilen übereinstimmt, als auch, wie die Regex-Engine selbst ausgeführt wird (eine Rückverfolgungsquelle wird dadurch eliminiert).
Nun, um das bisschen über "Backtracking" zu erklären. Stellen Sie sich vor, Sie haben eine Zeile mit acht Pipe-Zeichen:
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
In der folgenden Passage wird beschrieben, wie die Regex-Engine versucht, Ihren Ausdruck mit der obigen Zeile abzugleichen (Ich habe den Regex-Zeilen zusätzlichen Leerraum hinzugefügt, um (ungefähr) anzuzeigen, wo Teile des Regex mit den Zeichen der Zeile selbst übereinstimmen).
Der erste .*
ist gierig und passt alles bis zum Ende der Zeile an, so dass das Pipe-Zeichen unübertroffen bleibt.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |
Der jüngste "schrumpfbare" Abgleich gibt einen Teil des Abgleichs auf und versucht den Rest des regulären Ausdrucks erneut. Dies geschieht in diesem Fall jeweils .
einzeln (da jedes einzelne Zeichen übereinstimmt). Dieses Backtracking wird fortgesetzt, bis der Rest des Ausdrucks übereinstimmen kann (oder bis zum Anfang zurückgespult wird. Dies ist die einzige Möglichkeit, dass die Zeile dem Ausdruck nicht entspricht!).
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|
Der erste .*
Schritt war also genug, um den Rest der Gruppe zu ermöglichen, aber es gab nichts, was der zweiten Gruppe entsprach. Zeit, etwas mehr zurückzuverfolgen.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|
Das Backtracking hat einen neuen „Haltepunkt“ gefunden, aber jetzt macht der zweite .*
in der ersten Gruppe sein gieriges Matching. Die zweite Gruppe stimmt nicht überein. Die Rückverfolgung der Sekunde .*
in der ersten Gruppe beginnt.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.*)(.*|.* )(.*|
Die zweite Gruppe hat eine Übereinstimmung gefunden, die dritte Gruppe jedoch nicht. Backtrack nochmal, beginnend mit dem aktuelleren Spiel. Die zweite .*
der zweiten Gruppe geht zurück, um nichts zu erreichen. Die erste .*
Gruppe der zweiten Gruppe geht zu nichts zurück. Die zweite .*
Gruppe der ersten Gruppe geht auf nichts zurück. Die erste .*
der ersten Gruppe zieht sich erfolgreich zurück.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|
Aber die zweite .*
ist gierig, so dass nichts für die zweite Gruppe übrig bleibt.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|.* )(.*| aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.*)(.*|.*)(.*|.* )(.*|
Möglicherweise stimmen alle drei Gruppen überein, aber die vierte Instanz der Gruppe schlägt fehl. Beginnen Sie mit dem Backtracking.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*| aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|.* )(.*| aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|.*)(.*|.* )(.*| aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.*)(.*|.*)(.*|.*)(.*|.* )(.*|
Sie können sehen, wie viel Zeit verbrennt (die Diagramme überspringen sogar die tatsächlich auftretende Zeichen-für-Zeichen-Rückverfolgung; oben werden nur die "hohen Punkte" angezeigt). Das Problem entsteht dadurch, dass ein früher Teil des Regex gierig mit etwas übereinstimmt, mit dem der spätere Teil des Regex eventuell übereinstimmen muss, um die richtige Anzahl von Wiederholungen der Gruppe zu erhalten.
In meinem Ausdruck [^|]*
stimmt jede Wiederholung ( ) nie mit dem überein, auf das das folgende Element ( |
) passen würde, daher ist das Backtracking rein linear. Sobald das Backtracking für jede "schrumpfbare" Übereinstimmung beginnt, wird es (in linearer Zeit) feststellen, dass es keine früheren Stellen gibt, an denen der folgende Ausdruck passen kann. Dies zwingt das Backtracking dazu, mit dem vorherigen "schrumpfbaren" Spiel fortzufahren, bis nichts übereinstimmt und die gesamte Linie als nicht übereinstimmend gilt.
Anstelle von "null oder mehr Nichtpipe, dann Pipe" ( [^|]*|
) kann auch .
eine explizite nicht-gierige Wiederholung verwendet werden ( \{-}
in Vim variiert diese; andere Regex-Sprachen werden verwendet *?
).
^\(.\{-}|\)\.*$