Загадка preg_replace: замена нуля или более символа в конце темы - PullRequest
0 голосов
/ 06 августа 2010

Скажем, $ d - это путь к каталогу, и я хочу, чтобы он начинался и заканчивался ровно одной косой чертой (/).Изначально у него может быть ноль, один или несколько начальных и / или конечных слешей.

Я пробовал:

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

, что работает для ведущего слэша, но, к моему удивлению, дает two косая черта, если в $ d есть хотя бы одна косая черта.Если объект, например, 'foo///', тогда preg_replace () сначала сопоставляет и заменяет три завершающих слеша одним слешем, а затем соответствует нулевым слешам в конце и заменяет его слешем.(Вы можете проверить это, заменив второй аргумент на '[$0]'.) Я нахожу это довольно нелогичным.

Хотя есть много других способов решить основную проблему (и я ее реализовал), это стало головоломкой PCRE.для меня: какой (скалярный) шаблон в одном preg_replace делает эту работу?

ДОПОЛНИТЕЛЬНЫЙ ВОПРОС (правка)

Может кто-нибудь объяснить, почему этот шаблон совпадает с тем, как он работает в конце строки, но не ведет себяаналогично на старте?

Ответы [ 5 ]

3 голосов
/ 06 августа 2010
$path = '/' . trim($path, '/') . '/';

Сначала удаляются все косые черты в начале или в конце, а затем снова добавляются одиночные.

1 голос
/ 06 августа 2010

Учитывая регулярное выражение типа /*, которое может законно соответствовать нулевым символам, движок регулярных выражений должен убедиться, что он никогда не совпадет более одного раза в одном месте, иначе он застрянет в бесконечном цикле. Таким образом, если он потребляет ноль символов, двигатель переходит вперед на одну позицию перед попыткой другого совпадения. Насколько я знаю, это единственная ситуация, в которой движок регулярных выражений делает что-либо по собственной инициативе.

То, что вы видите, является противоположной ситуацией: регулярное выражение потребляет один или несколько символов, затем на следующем раунде оно пытается начать сопоставление в том месте, где оно остановилось. Не берите в голову, что это регулярное выражение не может соответствовать ничему, кроме одного символа, и оно уже соответствует такому числу, как могло; у него все еще есть возможность ничего не сопоставлять, так что это то, что он делает.

Итак, почему ваше регулярное выражение не совпадает дважды в начале, как это происходит в конце? Из-за начального якоря (^). Если субъект начинает с одного или нескольких слешей, он их потребляет, а затем пытается сопоставить ноль слешей, но это не удается, потому что он больше не находится в начале строки. И если в начале нет косых черт, то ручной сдвиг имеет тот же эффект.

На конце субъекта это другая история. Если там нет косых черт, это ничего не соответствует, пытается столкнуться и терпит неудачу; конец истории. Но если он действительно соответствует одному или нескольким слэшам, он использует их и пытается сопоставить снова - и успешно, потому что якорь $ все еще совпадает.

Таким образом, в общем, если вы хотите предотвратить такое двойное совпадение, вы можете добавить условие к началу совпадения, чтобы предотвратить его, как якорь ^ для первого альтернатива:

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

... или убедитесь, что часть регулярного выражения должна содержать хотя бы один символ:

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

Но в этом случае у вас есть гораздо более простой вариант, как продемонстрировал Джон Кугельман: просто захватите часть, которую вы хотите сохранить, и бросьте оставшуюся часть.

1 голос
/ 06 августа 2010
preg_replace('%^/*(.*?)/*$%', '/\1/', $d)
1 голос
/ 06 августа 2010

это можно сделать в одном preg_replace

preg_replace('/^\/{2,}|\/{2,}$|^([^\/])|([^\/])$/', '\2/\1', $d);
0 голосов
/ 06 августа 2010

Небольшое изменение в вашем шаблоне состояло бы в том, чтобы выделить две ключевые проблемы в конце строки:

  1. Заменить несколько косых черт одной косой чертой
  2. Заменить без косых чертодна косая черта

Шаблон для этого (и существующая часть для сопоставления в начале строки) будет выглядеть следующим образом:

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

Немного менее сжато, но более точно, опция должна быть очень точной в отношении совпадения только с нулем или двумя или более слешами;смысл в том, зачем заменять один слеш одним слешем?

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

Помимо: предложение nikic использовать trim (для удаления начальных / конечных слешей,затем добавьте свой собственный) хороший.

...