Вопрос или проблема
Существует ли лаконичное расширение параметров регулярного выражения zsh для замены последнего совпадения с шаблоном?
Я знаю очень неэстетичный способ сделать это, но предпочел бы более чистый и лаконичный синтаксис:
# param, pattern & replacement являются заполнителями для реальных значений
${(*S)param/%pattern(#b)(*)/replacement${match[1]}}
Это включает extended_glob, выбирает кратчайшее совпадение, сопоставляет шаблон с концом значения $param
, задает шаблон, включает обратные ссылки, сохраняет текст после последнего совпадения шаблона в обратной ссылке, а затем заменяет совпадение на мою замену, используя обратную ссылку для восстановления текста после совпадения.
Если под последним вхождением вы имеете в виду последнее, просканированное слева направо, как делает ${param//pattern/replacement}
(что не является тем, что делает ваша попытка), вы можете сделать так:
set -o extendedglob
param=foobarbazfoofoofoo pattern=foo replacement=replacement
: ${param//(#m)$~pattern}
param[MBEGIN,MEND]=$replacement
Функцию, которая заменяет nth вхождение (возможно, считая от конца, когда n отрицательное), можно написать так:
replace() {
set -o localoptions -o extendedglob
local _param=$1 _n=$2 _pat=$3 _repl=$4 _found MATCH MBEGIN MEND
local -a _b _e
: ${(P)_param//(#m)$~_pat/${_b[++_found]=$MBEGIN}${_e[_found]=$MEND}}
(( _n > 0 && _found >= _n )) ||
(( _n < 0 && - _found <= _n )) &&
eval $_param'[_b[_n],_e[_n]]=$_repl'
}
Чтобы вызвать, например, так:
replace param -1 $pattern $replacement
Нет явного способа выбрать последнее совпадение. Но вы можете сопоставить *
, за которым следует ваш шаблон: тогда ваш шаблон будет последним совпадением, поскольку *
соответствует максимально длинному префиксу, за которым следует ваш шаблон. Используйте b
флаг глоббирования, чтобы получить доступ к частям совпавшей строки в замене.
Это требует включения extended_glob
. С zsh ≥5.9 вы можете активировать его с помощью *
флага расширения параметров.
Например, следующий фрагмент сопоставляет a1b2c3d
с шаблоном (#b)(*)([0-9])
. (#b)
заставляет следующие обрамленные группы фиксировать то, что они сопоставляют в $match
. (Позиции также доступны в $mbegin
и $mend
, если они вам нужны.) *
соответствует самому длинному префиксу, так что [0-9]
соответствует последней цифре. Замена сохраняет префикс, соответствующий *
, заменяет цифру чем-то другим и сохраняет несопоставленный суффикс, так что фрагмент выводит a1b2c-d
.
setopt extended_glob
a=a1b2c3d
echo ${a/(#b)(*)([0-9])/$match[1]-}'
В zsh ≥5.9 вам не нужно удостоверяться, что extended_glob
активен.
a=a1b2c3d
echo ${(*)a/(#b)(*)([0-9])/$match[1]-}'
Ответ или решение
Вопрос о замене последнего вхождения шаблона с использованием регулярных выражений в Zsh вызывает интерес у многих разработчиков и системных администраторов. В данной статье подробно рассмотрим, как можно аккуратно и эффективно реализовать данную задачу с минимальными затратами на код.
Основные Принципы
Zsh поддерживает расширенные возможности для обработки строк, в том числе с использованием регулярных выражений и параметрного расширения. Возможности замены строк позволяют не только заменять все вхождения шаблона, но и выполнять специализированные операции, такие как замена последнего вхождения.
Рекомендуемый Подход
Для замены последнего вхождения шаблона в строке, используя Zsh, мы можем воспользоваться следующим методом, активируя опцию extendedglob
, чтобы иметь доступ к расширенным возможностям сопоставления.
Пример Кода
Рассмотрим следующий код:
setopt extended_glob
param="foobarbazfoofoofoo"
pattern="foo"
replacement="replacement"
: ${param//(#m)$~pattern}
param[MBEGIN,MEND]=$replacement
echo $param
Объяснение:
-
Активируем
extended_glob
: Эта опция позволяет использовать расширенные шаблоны для сопоставления. Это необходимо для работы с конструкциями(#m)
и${...}
. -
Инициализация параметров: Переменная
param
содержит строку, в которой мы хотим произвести замену. Параметрpattern
определяет шаблон, аreplacement
— текст, на который мы хотим заменить последнее вхождение данного шаблона. -
Замена в последнем вхождении:
- Используя конструкцию
${param//(#m)$~pattern}
, мы сначала удаляем все вхождения шаблона из строкиparam
. - Затем с помощью
param[MBEGIN,MEND]=$replacement
мы возвращаем замененный текст на позициях, соответствующих найденному шаблону.
- Используя конструкцию
-
Вывод результата: В конечном итоге
echo $param
выдаст результат с заменой последнего вхождения.
Расширенный Функционал
Если вам необходимо заменить, скажем, n-е вхождение (положительное с начала строки или отрицательное от конца), вы можете использовать следующую функцию:
replace() {
set -o localoptions -o extendedglob
local _param=$1 _n=$2 _pat=$3 _repl=$4 _found MATCH MBEGIN MEND
local -a _b _e
: ${(P)_param//(#m)$~_pat/${_b[++_found]=$MBEGIN}${_e[_found]=$MEND}}
(( _n > 0 && _found >= _n )) ||
(( _n < 0 && -_found <= _n )) &&
eval $_param'[_b[_n],_e[_n]]=$_repl'
}
# Использование функции
param="foobarbazfoofoofoo"
pattern="foo"
replacement="replacement"
replace param -1 $pattern $replacement
echo $param
Заключение
Использование Zsh для замены последнего вхождения шаблона — это мощный инструмент для работы с строками. Благодаря возможности регулярных выражений и параметрного расширения разработчики могут эффективно обрабатывать данные, не прибегая к сложным и запутанным решениям. Опираясь на описанные подходы, вы сможете значительно упростить свои скрипты и сделать их более читабельными и элегантными.