2010-08-05 12 views
0

Say $ d est un chemin de répertoire et je veux m'assurer qu'il commence et se termine exactement par une barre oblique (/). Il peut initialement avoir zéro, une ou plusieurs barres obliques de début et/ou de fin.Un puzzle preg_replace: remplacer zéro ou plus d'un caractère à la fin du sujet

J'ai essayé:

preg_replace('%^/*|/*$', '/', $d); 

qui travaille pour la barre oblique, mais à ma grande surprise des rendements deux barres obliques de fuite si $ d a au moins un slash. Si le sujet est, par exemple, 'foo///', alors preg_replace() correspond d'abord et remplace les trois barres obliques finales par une barre oblique, puis il correspond à zéro barre oblique à la fin et remplace cela par une barre oblique. (Vous pouvez le vérifier en remplaçant le deuxième argument par '[$0]'.) Je trouve cela plutôt contre-intuitif. Bien qu'il existe de nombreuses autres façons de résoudre le problème sous-jacent (et j'en ai implémenté une), cela est devenu un casse-tête PCRE: quel modèle (scalaire) dans un seul preg_replace fait ce travail?

QUESTION ADDITIONNELLE (modifier) ​​

Quelqu'un peut-il expliquer pourquoi ce modèle correspond à la façon dont il le fait à la fin de la chaîne, mais ne se comporte pas de la même au début?

Répondre

1

donné une regex comme /* qui peut légitimement correspondre à zéro caractères, le moteur regex doit vous assurer qu'il ne correspond jamais à plus d'une fois au même endroit, ou il se coincer dans une boucle infinie. Ainsi, s'il consomme zéro caractères, le moteur saute d'une position avant d'essayer une autre correspondance.Pour autant que je sache, c'est la seule situation dans laquelle le moteur regex fait quelque chose de sa propre initiative. Ce que vous voyez est la situation inverse: la regex consomme un ou plusieurs caractères, puis au prochain tour, elle essaie de commencer à correspondre à l'endroit où elle s'est arrêtée. Peu importe que cette regex particulière ne puisse correspondre à rien d'autre qu'à un seul caractère, et qu'elle en corresponde déjà autant qu'elle le pouvait; il a toujours la possibilité de ne rien faire, alors c'est ce qu'il fait. Donc, pourquoi votre regex ne correspond pas deux fois au début, comme c'est le cas à la fin? En raison de l'ancre de départ (^). Si le sujet commence par une ou plusieurs barres obliques, il les consomme, puis essaie de faire correspondre les barres obliques , mais cela échoue car ce n'est plus au début de la chaîne. Et si sont aucune barre oblique au début, le bump-along manuel a le même effet. À fin du sujet c'est une histoire différente. S'il n'y a pas de barres obliques là-bas, cela ne correspond à rien, essaie de bousculer et échoue; fin de l'histoire. Mais si correspond à une ou plusieurs barres obliques, il les consomme et essaie de correspondre à nouveau - et réussit car l'ancre $ correspond toujours.

Donc, en général, si vous voulez éviter ce genre de double match, vous pouvez ajouter une condition à la commençant du match pour l'empêcher, comme l'ancre ^ fait pour la première alternative:

preg_replace('%^/*|(?<!/)/*$%', '/', $d); 

... ou assurez-vous qu'une partie de la regex doit consommer au moins un caractère:

preg_replace('%^/*|([^/])/*$%', '$1/', $d); 

Mais dans ce cas, vous avez une option beaucoup plus simple, comme le montre John Kugelman: juste capturer la partie que vous voulez garder et jeter le reste.

+0

+1 belle explication.Bien que pour moi le comportement du moteur regex n'est toujours pas intuitif dans ce cas. – NikiC

+0

Belle exposition. Merci, Alan. Bien que cela ait du sens quand vous le décrivez, je doute que je sois capable de me souvenir de cela la prochaine fois que quelque chose comme ça arrivera - la chose contre-intuitive. Mais c'est ici pour référence future. –

3
$path = '/' . trim($path, '/') . '/'; 

Ceci supprime d'abord toutes les barres obliques au début ou à la fin, puis ajoute à nouveau les simples.

+1

cela peut être plus rapide qu'une regex mais il a explicitement demandé une regex et pas une autre façon de résoudre le problème. –

+1

Bien que je suis d'accord que la question exacte a demandé regex, mais PHP offre une meilleure solution que regex dans ce cas. De toute façon, l'OP obtient à la fois des réponses regex et une bonne solution spécifique PHP. OP peut choisir la validité en choisissant une réponse plutôt qu'une autre. – Tim

+0

oui, c'est pourquoi j'ai dit trim et concat peuvent être plus rapides;) –

1

il peut être fait en une seule preg_replace

preg_replace('/^\/{2,}|\/{2,}$|^([^\/])|([^\/])$/', '\2/\1', $d); 
+0

Nice. Que diriez-vous d'améliorer la lisibilité en supprimant toutes les barres obliques inverses: 'return preg_replace ('! ^/{2,} |/{2,} $ |^([^ /]) | ([^ /]) $!', '$ 2/$ 1 ', $ d); –

+0

oui ofc, mais je suis simplement habitué à perl qui ne permet que des barres obliques comme délimiteur –

+0

Il m'a fallu quelques efforts pour comprendre cela. Il est similaire à la réponse de salathe mais utilise '^ ([^ /])' et '([^ /]) $' à la place des assertions en ajoutant les caractères capturés. J'admire la complexité. –

1
preg_replace('%^/*(.*?)/*$%', '/\1/', $d) 
+0

Celui-ci est bien aussi. '$' au lieu de '\' facilite la lecture de imo: 'preg_replace ('% ^/* (. *?)/* $%', '/ $ 1 /', $ d);' –

+0

hah, si facile et rapide :) –

+0

Inverser la pensée, c'est-à-dire capturer ce que vous voulez garder plutôt que ce que vous voulez remplacer, et soudain, c'est évident. Excellent! –

0

Un petit changement à votre modèle serait de séparer les deux principales préoccupations à la fin de la chaîne:

  1. Remplacer plusieurs barres obliques par une barre oblique
  2. Remplacer les barres obliques par une barre oblique unique

Un motif pour que (et la partie existante pour faire correspondre au début de la chaîne) ressemblerait à ceci:

#^/*|/+$|$(?<!/)# 

Un peu moins concis, mais plus précis, l'option serait d'être très explicite sur seulement correspondant à zéro ou à deux barres ou plus; l'idée étant, pourquoi remplacer une barre oblique par une barre oblique?

#^(?!/)|^/{2,}|/{2,}$|$(?<!/)# 

En plus: nikic's suggestion à utiliser trim (pour éliminer les principaux/barres obliques arrière, puis ajoutez votre propre) est un bon.

+0

Très bien. Cette réponse s'adresse le plus directement à la surpise que j'ai vu à la fin de la chaîne dans ma première tentative. En ce moment, je suis déchiré entre accepter cela et la réponse de John Kugelman. Votre deuxième version est, je suis d'accord, précise, où John fait parfois un travail inutile. Mais John's est très simple, approchant celui de Nikki. –