Примеры из практики.

Замена некоторых символов в некоторых выбранных подстроках.

Часто нужно заменить не все символы в строке, а только некоторые, те, что попадают под заданное регулярное выражение. К примеру, недавно мне потребовалось заменить выражение /%5B/ на скобку «[», но не везде, а лишь НЕ внутри ED2K ссылок. Как это сделать?

Ясно, что простая команда s тут не применима, следует воспользоваться каким-то другим приёмом, лично я использовал технику маркёров - я попросту стёр все места где замена не требуется, и вместо них вставил туда маркёрные байты. После чего и произвёл замену:s/%5B/[/g; s/%5D/]/g Как видите - всё просто. Команда удаления тех мест, где замена не нужна тоже тривиальна: s~\[url=ed2k://[^]]+\]~\r~ig (подразумевается, что мы уже очистили текст от «\r»).

Теперь нам нужно поменять маркёры на нужные подстроки. Сделать это не так уж и сложно, проблема лишь в их очерёдности. В силу того, что регулярное выражение /.*МАРКЁР/ захватывает все маркёры до последнего, проще всего как раз с последнего и начать, и заменить их все в цикле, я это сделал так:

:l4
	# вставляем последнюю ссылку на своё место в цикле
	s~^(.*)\r([^\n]*)\n(.*)(\[url=ed2k://[^]]+\])(.*)$~\1\4\2\n\3\5~i
	t l4

Как видите, ничего архисложного - тут у меня два особых символа: «\r» я использую в качестве маркёра, а вот «\n» используется в качестве разделителя. В выражении для поиска дважды встречается /.*/ - мы ищем именно последнее совпадение (третье вхождение /.*/ я не считаю - это просто хвост, который надо сохранять).

Безусловно, я приведу и весь скрипт целиком:

/%5B|%5D/{
	#замена скобок обратно. Заменяются только вне bbcode
	\~\[url=ed2k://~{
		# у нас есть ed2k ссылки, сохраняем строку
		h
		# вырезаем ссылки
		s~\[url=ed2k://[^]]+\]~\r~ig
		# меняем скобки и палки
		s/%5B/[/g
		s/%5D/]/g
		# добавляем старую строку
		G
		t l4
		:l4
			# вставляем последнюю ссылку на своё место в цикле
			s~^(.*)\r([^\n]*)\n(.*)(\[url=ed2k://[^]]+\])(.*)$~\1\4\2\n\3\5~i
			t l4
		# отрезаем ненужный хвост
		s/\n.*//
		b l5
	}
	s/%5B/[/g
	s/%5D/]/g
	:l5
}

Ещё один способ замены некоторых подстрок в выбранных подстроках. При использовании мультистрочного текста.

Недавно мне попалась похожая с прошлым примером задача: имелась большая строка (1-50Кбайт), которая представляет собой некий текст. Этот текст вообще говоря являлся HTML текстом, но с некоторым исключением: в обычном HTML все последовательности пробельных символов (« », «\t», «\r», и «\n») эквивалентны одному пробелу, но есть и исключение:

Некоторый текст можно заключить в теги <pre></pre>, и тогда этот текст должен быть выведен "как есть". Моя задача была преобразовать этот HTML в текст, причём ограничителем "кода" выступало очень сложное регулярное выражение (скорее очень громоздкое). И кроме того, я не мог использовать символ «\n» в качестве разделителя.

Для начала я стёр 2 символа, которые я решил использовать в качестве маркёров:

s/@/<а>/g
s/~/<я>/g
				

(обратите внимание: буква «а» тут русская!)

Теперь я имею 2 маркёрных символа, чего мне вполне хватит (именно эти символы были выбраны потому, что они не являются метасимволами regex'пов, и их не надо экранировать). Теперь я могу заменить начальные и конечные части выражения на маркёры:

s~BEGIN~@<код>~g
s%END%</код>~%g
				

Замечание

Словом BEGIN обозначено начало особого участка, словом END - его конец, и также вводятся фиктивные теги для ограничения границ участка, они впоследствии будут заменены.

Получается вот что:

текст1@код1~текст2@код2~текст3
				

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

h
s/@[^~]*~/@/g
# обработка обычного текста
x
s/^/~/
s/$/@/
s/~[^@]*@/~/g
# обработка <код>
x
				

Тут я в обычном тексте меняю куски "кода" на @, а в "коде" меняю обычный текст на ~. Кроме того, в "коде" я ещё и добавляю в начале и в конце строки (точнее всего текста, я уже говорил, он у меня целиком в буфере) символ ~.

Теперь осталось собрать эти два текста воедино. На самом деле это не сложно:

текст1@текст2@текст3
~код1~код2~
				

Как и в прошлом примере, объёденим строчки, но в данном примере нам ещё нужно удалить лишний символ перевода строки (его втоматически добавляет команда G).

G
s/\n~/~/
				

(тут используется тот факт, что в строке с "текстом" заведомо нет символов ~, а команда s/// меняет лишь первое совпадение. получаем следующее:

текст1@текст2@текст3~код1~код2~
				

Осталось заменить «@» на выделенный и обработанный "код". Как и в прошлом примере, меняем по одному фрагменту, начиная с последнего. Фрагмент «~код» переносится вместо «@».

t l8
:l8
	s/^(.*)@([^~]*)(.*)~([^~]*)~$/\1\4\2\3~/
	t l8
s/~$//
T error
/[~@]/ b error
s/<а>/@/g
s/<я>/~/g
				

После замены остаётся символ ~ в конце строки, который и удаляется. На всякий случай проверяется наличие оставшихся маркёров (хотя их вроде быть не может). Ну и после замены спец-тегов скрипт завершается.

Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).

Вы можете пожертвовать небольшую сумму яндекс-денег на счёт 41001666004238 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.