Найти тему
Блокнот математика

Рубрика "Секреты Вим". Практика замены

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

Для начала простой пример, описанный в Справке. Пример на якоря границ слова: %s/\<four\>/4/g. Так вы замените только то, что нужно.

Можно дать команду замены на выделении. Только помните, что команда работает построчно, так что если вы выделили текст от середины одной строки до середины другой, то под замену попадут обе целиком. Еще жестче это при выделении блока. Чтобы совпадение искалось только в выделенном тексте, используйте ключ \%V. Это якорь, и позиция, в которой он стоит, должна быть внутри выделения. Можете окаймить выражение этими якорями и тогда оно всё должно быть внутри:

:s/\%VFrancesca%V/Paola/

Это мне сильно пригодилось, когда я вручную правил карту глубин моря в текстовом формате. Она очень большая. Мне нужно было засыпать озеро, а для этого надо заменить нулями двузначные числа глубин. Естественно, вся строка мне совершенно не нужна, только выделенный прямоугольник. Но он довольно велик, так что вручную это не классно. Ну, команда

:s/\%V\d\d\%V/00/g

на выделении сработала как надо!

Вот так. Выделил прямоугольник, совпадение только в нем, и всё отлично получается.
Вот так. Выделил прямоугольник, совпадение только в нем, и всё отлично получается.

Пользуйтесь якорями. Например, так можно закомментировать несколько строк (комментарий в фортране от ! до конца строки):

:.,+7s/^/!/

А вот как можно убрать пробелы в конце строк:

:%s/\s\+$//

Очень полезны якоря "начало совпадения" \zs и "конец совпадения" \ze. Они совпадают всегда, но отмечают начало и конец совпадения. Все, что левее начала и правее конца — должно совпасть, но совпадением не считается! Это очень удобно и заменяет различные проверки (которые, впрочем, тоже имеются в Вим). Примеры:

:s/^\s*integer.*\zsvariable/x/

Cовпадение пойдет от начала строки ^, потом могут быть пробелы, потом данный текст (integer), потом что угодно... но это совпадение "не считается" и заменено не будет.

:s/(\zs[^()]*\ze)/66/

Заменим содержимое скобок. Сами скобки не трогаем, но они должны быть.

:%s!^.\+\zs//.*!!

Удаляет комментарии в стиле С++. От начала строки что угодно — этот текст должен присутствовать, но не будет удален. При этом комментарии от начала строки (описания) не удаляются.

Очень полезны захваты. Текст ловят скобки вида \(...\) и он доступен в том же выражении и в выражении замены как \1, \2, ..., \9. В отличие от Перл, в выражении замены это тоже \1 (а не $1) и их может быть не более девяти (а не сколько угодно). Это важно, так как

:s/\(\<\d\>\)/\10/

припишет к одинокому числу нолик, а не отправится за десятой скобкой (которой и нет). Можно это проделать и проще:

:s/\<\d\>\zs/0/

Номер скобки определяется по номеру открывающей скобки. Выражение \0 означает "все совпадение", как и &. Вот еще вариант:

:s/\<\d\>/&0/

Пример посложнее:

:%s/\$\$\(\_.\{-}\)\$\$/\\begin{equation*}\1\\end{equation*}/c

Заменяем формулы в двойных долларах на Латех-окружение. С подтверждением каждого случая. Обратите внимание на:

  1. Символ "всё, включая конец строки" \_. Вообще, все классы символов (включая и точку, которая означает "любой символ") можно расширить, включив туда конец строки, путем добавления \_
  2. Ленивый квантификатор \{-}, который означает "нуль или более", как и *, но, в отличие от нее, "как можно меньше". Связка \_.\{-} означает "что угодно после начала, но только до конца!". Первый же $$ после открывающего позволит прекратить поиск. Нам то и надо.
  3. Экранирование \$ и \\.

А вот так можно заменить ряд визуально пустых строк одной пустой:

:%s/^\_s*$//

Это сложный пример, убедитесь, что вы его поняли. Символ \_s совпадает с пробелом (любым невидимым символом) или символом конца строки. Квантификатор * забирает столько, сколько сможет. Он и заберет все пустые строки. На последней пустой строке он дойдет до символа конца строки и заберет и его, но потом должен совпасть якорь $ (конец строки). А мы уже на начале новой, поэтому звездочка уступит один символ: конец строки. Теперь все совпадет. Именно то, что нам надо: все пустые (с пробелами или совсем пустые) строки, кроме последнего символа конца строки, который останется. Он создаст одну пустую строку.

Как отловить два слова подряд? Частая опечатка: the the и тому подобное. Несложно:

:%s<\(\w\+\)\>\s\+\1/\1/gc

Ищем слово и захватываем его, потом должен быть пробел, потом захваченное слово повторно. Заменяем всё совпадение на захваченное слово. Стоит флаг подтверждения, ибо иногда так и задумано: that that может быть легально!

Нюанс: если вы не придерживаетесь стиля "одно предложение на одну строку", то используйте \_s, чтобы суметь отыскать опечатку, если между словами стоит конец строки.

Еще одна практическая задача: поменять местами инициалы и фамилию: чтобы "A. Einstein" стал "Einstein, A." Это несложно:

:s/\(\w\.\%(\s*\w\.\)*\)\s\+\(\w\w\+\)/\2, \1/g

Что тут интересно: группировка без захвата скобками \%(...\): эти скобки позволяют применить к ним квантификатор *, но текст не захватывают и в счет не идут; символ "точка" в виде \.

Класс \w совпадает со "словными символами", куда русские не входят. Это несложно поправить, заменив везде \w на [a-zа-яё]. Громоздко, но что поделать.

Поделать-то кое-что можно: класс \k совпадает и с русскими буквами. Он вообще для символов, допустимых в ключевых словах. И его можно настроить через опцию iskeyword. Если вы работаете с обычным текстом, то эта опция как нельзя более кстати, чтобы сделать себе русский \w.

Иногда пригождается вычисляемое выражение замены. Строка замены рассматривается как выражение, если начинается с символа \=. В этом выражении есть доступ к найденному тексту и захватам — через функцию submatch. Вот как можно выровнять числа в столбцах, или укоротить числа с округлением, чтобы таблица была покомпактнее:

:s/[0-9.-]\+/\=printf("%5.2f", str2float(submatch(0)))/g

Ищем любой набор из цифр, может быть ещё точка и знак минус (число можно описать и более точно, конечно). Преобразуем в дробное число (параметр %f), указывая минимальную длину (5, имейте в виду, что число может быть длиннее!) и число цифр после запятой (.2). Необходимо перевести результат submatch в числовую форму с плавающей точкой: автоматически это не делается.

Вот с такими таблицами приходится часто работать
Вот с такими таблицами приходится часто работать

Пользуйтесь незахватывающими скобками \%(...\). С их помощью можно группировать выражения и делать сложные вещи. Например,

:s/\%([0-9.-]\+\s\+\)\{5}\zs[0-9.-]\+/\=str2float(submatch(0))*1.05/

У нас таблица чисел, разделенных пробелами. Число описано классом [0-9.-]\+, то есть состоит из цифр, точки и знака "минус". После числа идет пробел. Таких чисел мы ищем пять (квантификатор \{5}), а потом начинам совпадение. То есть, совпадает шестой столбец. Совпадение мы переводим в числовую форму и умножаем на 1.05, то есть увеличиваем на 5 процентов.

Удачи, коллеги!

Научно-популярные каналы на Дзене: путеводитель
Новости популярной науки12 марта 2022