В Vim, как я могу искать и заменять каждый другой матч?
Скажем, у меня есть следующий файл
<block>
<foo val="bar"/>
<foo val="bar"/>
</block>
<block>
<foo val="bar"/>
<foo val="bar"/>
</block>
Как я могу сделать это в
<block>
<foo val="bar1"/>
<foo val="bar"/>
</block>
<block>
<foo val="bar1"/>
<foo val="bar"/>
</block>
Одна вещь, которую я пытался сделать, это записать макрос с :%s/bar/bar1/gc
и нажать y
и n
один раз, а затем попытаться отредактировать этот макрос. По какой-то причине я не могу редактировать макрос.: (
Ответы
Ответ 1
Просто чтобы показать, что это можно сделать в подстановке:
:let a = ['', '1']
:%s/bar\zs/\=reverse(a)[0]/g
Обзор
Замените в конце каждого bar
первым элементом массива в переменной a
после того, как массив будет заменен на месте при каждой подстановке.
Слава деталей
-
let a = ['', '1']
определить переменную a
для хранения нашего массива
-
%s/.../.../
выполнить подстановку в каждой строке файла
-
%s/bar\zs/.../
выполните подстановку на панели, но запустите замену после строки с помощью \zs
-
\=
внутри заменяющей части команды :s
используется значение следующего выражения
-
reverse(a)
reverse просто меняет массив, но делает это на месте
-
reverse(a)[0]
reverse возвращает теперь обратный массив, поэтому получите первый элемент
-
/g
заменить все события в строке (необязательно)
Общий случай
:let a = ['a', 'b', 'c']
:%s/bar\zs/\=add(a, remove(a, 0))[-1]/g
Общий случай "вращает" массив, a
, на месте и использует последнюю позицию массива как значение для замены замены.
Подробнее см.
:h :s
:h range
:h /\zs
:h :s\=
:h reverse(
:h :s_flags
:h Lists
:h add(
:h remove
Ответ 2
Вы можете использовать
:%s/bar/bar1/gc
И он попросит вас в каждом матче, если вы хотите его заменить.
Иначе вы должны указать весь контент и просто заменить первый бар на bar1.
Ответ 3
Я бы сделал это с помощью макроса:
qv start recording in register v
/"bar"/e<cr> search for "bar" and position the cursor at the end of the match
i1<esc> insert 1 before the cursor and go back to normal mode
n jump to next match
q stop recording
После этого сделайте {count}@v
.
Ответ 4
попробуйте следующее:
:%s/bar\(.*\)\n\(.*\)bar/bar1\1\r\2bar
Ответ 5
Здесь пользовательская команда, которая должна делать трюк. Он использует выражение replace для подсчета выполненных замещений и использует переданный дополнительный аргумент для определения необходимости замены. (Это позволяет создавать более сложные схемы, чем каждый второй). Ваш пример будет простым:
:%SubstituteSelected/\<bar\>/&1/ yn
Здесь (к сожалению, довольно длинная) реализация:
":[range]SubstituteSelected/{pattern}/{string}/[flags] {answers}
" Replace matches of {pattern} in the current line /
" [range] with {string}, determining whether a particular
" match should be replaced on the sequence of "y" or "n"
" in {answers}. I.e. with "ynn", the first match is
" replaced, the second and third are not, the fourth is
" again replaced, ...
" Handles & and \0, \1 .. \9 in {string}.
function! CountedReplace()
let l:index = s:SubstituteSelected.count % len(s:SubstituteSelected.answers)
let s:SubstituteSelected.count += 1
if s:SubstituteSelected.answers[l:index] ==# 'y'
if s:SubstituteSelected.replacement =~# '^\\='
" Handle sub-replace-special.
return eval(s:SubstituteSelected.replacement[2:])
else
" Handle & and \0, \1 .. \9 (but not \u, \U, \n, etc.)
let l:replacement = s:SubstituteSelected.replacement
for l:submatch in range(0, 9)
let l:replacement = substitute(l:replacement,
\ '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!' .
\ (l:submatch == 0 ?
\ '\%(&\|\\'.l:submatch.'\)' :
\ '\\' . l:submatch
\ ),
\ submatch(l:submatch), 'g'
\)
endfor
return l:replacement
endif
elseif s:SubstituteSelected.answers[l:index] ==# 'n'
return submatch(0)
else
throw 'ASSERT: Invalid answer: ' . string(s:SubstituteSelected.answers[l:index])
endif
endfunction
function! s:SubstituteSelected( range, arguments )
let l:matches = matchlist(a:arguments, '^\(\i\@!\S\)\(.*\)\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\1\(.*\)\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\1\(\S*\)\s\+\([yn]\+\)$')
if empty(l:matches)
echoerr 'Invalid arguments'
return
endif
let s:SubstituteSelected = {'count': 0}
let [l:separator, l:pattern, s:SubstituteSelected.replacement, l:flags, s:SubstituteSelected.answers] = l:matches[1:5]
execute printf('%ssubstitute %s%s%s\=CountedReplace()%s%s',
\ a:range, l:separator, l:pattern, l:separator, l:separator, l:flags
\)
endfunction
command! -bar -range -nargs=1 SubstituteSelected call <SID>SubstituteSelected('<line1>,<line2>', <q-args>)
Изменить
Теперь я опубликовал это (вместе со связанными командами) как плагин PatternsOnText.
Ответ 6
:let dosubs=1
:%s/bar/\=[dosubs?'bar1':submatch(0),extend(g:,{'dosubs':!dosubs})][0]/g
Ответ 7
Я нашел более простое решение:
:g/<block>/norm! j02f"i1
:g ............ global command
/<block>/ ..... every line with <block>
j0 ............ goes down one line and to the column 1
2f" ........... jumps to the second "
i1 ............ insert the number one
Мое старое сложное решение
\v%(block>\_{-})\zsbar
%s,,&1,g
\v ............ very magic (avoid lots of scapes)
% ............ ignore whats flows
( ............ starts (ignored) group
\_ ............ multiline search
.{-} .......... non-greedy
\zs ........... start pattern for substituition
bar .......... pattern we want to change
% ............. whole file
s ............. substituition
,, ............ use last search (could be //)
& ............. use searched pattern
1 ............. add 1 after it