Скажем, $ d – путь к каталогу, и я хочу, чтобы он начинался и заканчивался ровно одной косой чертой (/). Первоначально он может иметь ноль, одну или несколько ведущих и / или конечных косых черт.
Я пытался:
preg_replace('%^/*|/*$', '/', $d);
который работает для ведущей косой черты, но, к моему удивлению, дает две конечные слэши, если $ d имеет хотя бы одну конечную косую черту. Если субъект, например, 'foo///'
то preg_replace () сначала соответствует и заменяет три конечных косая черта одной косой чертой, а затем соответствует нулевым сокращениям в конце и заменяет их косой чертой. (Вы можете проверить это, заменив второй аргумент на '[$0]'
.) Я нахожу это довольно противоречивым.
Хотя есть много других способов решить основную проблему (и я ее реализовал), это стало загадкой PCRE для меня: какой (скалярный) шаблон в одном preg_replace
выполняет эту работу?
ДОПОЛНИТЕЛЬНЫЙ ВОПРОС (изменить)
Может ли кто-нибудь объяснить, почему этот шаблон соответствует тому, как он работает в конце строки, но не ведет себя аналогично в начале?
Учитывая регулярное выражение, как /*
которое может законно соответствовать нулевым символам, механизм регулярных выражений должен удостовериться, что он никогда не совпадает более одного раза в одном и том же месте, или он застрял бы в бесконечном цикле. Таким образом, если он потребляет нулевые символы, двигатель перескакивает вперед на одну позицию перед попыткой другого совпадения. Насколько я знаю, это единственная ситуация, когда движок регулярных выражений делает что-либо по собственной инициативе.
То, что вы видите, это противоположная ситуация: регулярное выражение потребляет один или несколько символов, затем в следующий раунд он пытается начать сопоставление в том месте, где оно остановилось. Не обращайте внимания на то, что это конкретное регулярное выражение не может сравниться ни с чем, кроме одного персонажа, и оно уже сопоставлено со многими из них; он по-прежнему имеет возможность ничего не сопоставлять, так вот что он делает.
Итак, почему ваше регулярное выражение не совпадало дважды в начале, как это происходит в конце? Из-за стартового якоря ( ^
). Если объект начинается с одной или нескольких косой черты, он их потребляет, а затем пытается совместить нулевые слэши, но он терпит неудачу, потому что он больше не находится в начале строки. И если в начале нет косых черт, ручная подача имеет тот же эффект.
В конце темы это совсем другая история. Если там нет косых черт, оно ничего не соответствует, пытается наброситься вперед и провалиться; конец истории. Но если он соответствует одному или нескольким косым чертам, он их потребляет и пытается снова сопоставляться – и преуспевает, потому что привязка $
прежнему совпадает.
Таким образом, в общем случае, если вы хотите предотвратить такое двойное совпадение, вы можете либо добавить условие к началу матча, чтобы предотвратить его, например, якорь ^
для первого варианта:
preg_replace('%^/*|(?<!/)/*$%', '/', $d);
… или убедитесь, что часть регулярного выражения должна потреблять по крайней мере один символ:
preg_replace('%^/*|([^/])/*$%', '$1/', $d);
Но в этом случае у вас есть гораздо более простой вариант, как продемонстрировал Джон Кугельман: просто захватите часть, которую вы хотите сохранить, и отбросьте все остальное.
$path = '/' . trim($path, '/') . '/';
Сначала удаляются все слэши в начале или в конце, а затем снова добавляются одиночные.
preg_replace('%^/*(.*?)/*$%', '/\1/', $d)
это может быть сделано в одном preg_replace
preg_replace('/^\/{2,}|\/{2,}$|^([^\/])|([^\/])$/', '\2/\1', $d);
Небольшое изменение в вашем шаблоне будет заключаться в том, чтобы выделить две основные проблемы в конце строки:
Шаблон для этого (и существующей части для совпадения в начале строки) будет выглядеть так:
#^/*|/+$|$(?<!/)#
Немного менее краткий, но более точный вариант должен быть очень явным только для соответствия нуля или двух или более слэшей; что бы заменить одну косую черту одной косой чертой?
#^(?!/)|^/{2,}|/{2,}$|$(?<!/)#
Кроме того: предложение nikic использовать trim
(для удаления ведущих / конечных косых черт, а затем добавить свои собственные) является хорошим.