2010-08-05 2 views
0

Скажите, что $ d - путь к каталогу, и я хочу, чтобы он начинался и заканчивался ровно одной косой чертой (/). Первоначально он может иметь ноль, одну или несколько ведущих и/или конечных косых черт.головоломка preg_replace: замена нуля или более символа в конце объекта

Я пробовал:

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

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

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

ДОПОЛНИТЕЛЬНЫЙ ВОПРОС (редактировать)

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

ответ

1

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

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

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

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

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

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

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

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

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

+0

+1 приятное объяснение.Хотя для меня поведение двигателя регулярных выражений по-прежнему неинтуитивно. – NikiC

+0

Прекрасная экспозиция. Спасибо, Алан. Хотя это имеет смысл, поскольку вы описываете это, я сомневаюсь, что смогу вспомнить это в следующий раз, когда что-то вроде этого появляется - противоречивая вещь. Но он здесь для справок в будущем. – 2010-08-06 20:27:20

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

Это первое удаление всех слэшей в начале или конце, а затем добавление одиночных.

+1

это может быть быстрее, чем регулярное выражение, но он явно запрашивается для регулярного выражения и не является другим способом решения проблемы. –

+1

Хотя я согласен с тем, что заданный вопрос задан для регулярного выражения, но PHP предлагает лучшее решение, чем регулярное выражение в этом случае. В любом случае, OP получает как регулярные ответы, так и хорошее PHP-решение. OP может выбрать правильность, выбирая один ответ над другим. – Tim

+0

да, вот почему я сказал, что обрезка и concat могут быть быстрее;) –

1

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

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

Ницца. Как насчет повышения удобочитаемости путем удаления всех обратных косых черт: 'return preg_replace ('! ^/{2,} |/{2,} $ |^([^ /]) | ([^ /]) $!', '$ 2/$ 1 ', $ d); '? –

+0

yes ofc, но я просто привык к perl, который разрешает только косые черты как разделитель –

+0

Мне потребовалось некоторое усилие, чтобы понять это. Это похоже на ответ salathe, но использует '^ ([^ /])' и '([^ /]) $' вместо утверждений, добавляющих захваченные символы назад. Я восхищаюсь сложностью. – 2010-08-06 00:40:05

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

Это тоже хорошо. '$' вместо '\' упрощает чтение imo: 'preg_replace ('% ^/* (. *?)/* $%', '/ $ 1 /', $ d);' –

+0

hah, так легко и быстро :) –

+0

Инвертировать мышление, т. е. захватывать то, что вы хотите сохранить, а не то, что хотите заменить, и вдруг это очевидно. Отлично! – 2010-08-06 00:44:07

0

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

  1. Заменить несколько слэшей одной косой чертой
  2. Заменить косые черты одной косой чертой

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

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

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

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

Помимо: nikic's suggestion использовать trim (для удаления ведущей/ведомой слэш, а затем добавить свой собственный) является хорошим.

+0

Очень хорошо. Этот ответ наиболее непосредственно затрагивает листинг, который я видел в конце строки в моей первой попытке. В настоящий момент я разорван между принятием этого и ответом Джона Кугельмана. Ваши второй версии, я согласен, точны, где Джон иногда делает ненужную работу. Но Джон очень прост, приближается к тикику. – 2010-08-06 00:54:06