在Vim中,我如何搜索和替换每隔一个匹配?

xcitsw88  于 2022-12-04  发布在  其他
关注(0)|答案(7)|浏览(134)

假设我有以下文件

<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录制一个宏,然后按yn各一次,然后试着编辑这个宏。由于某种原因,我不能编辑这个宏。

hfyxw5xn

hfyxw5xn1#

为了说明这可以在替换中完成:

:let a = ['', '1']
:%s/bar\zs/\=reverse(a)[0]/g

概述

在每次替换时原地反转数组后,用变量a中数组的第一个元素替换每个bar的末尾。
细节的荣耀

  1. let a = ['', '1']定义一个变量a来保存数组
  2. %s/.../.../对文件中的每一行进行替换
  3. %s/bar\zs/.../在条形图上进行替换,但在条形图之后使用\zs开始替换
  4. :s命令的替换部分内的\=使用以下表达式的值
  5. reverse(a) reverse只会反转数组,但会就地执行此操作
  6. reverse(a)[0] reverse返回现在已反转的数组,因此获取第一个元素
  7. /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
w46czmvw

w46czmvw2#

您可以使用

:%s/bar/bar1/gc

它会在每一场比赛中问你,如果你想取代它。
否则,您必须在中指定整个内容,并仅将第一个栏替换为bar1。

n3h0vuf2

n3h0vuf23#

我会用一个宏来实现:

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

acruukt9

acruukt94#

试试这个:

:%s/bar\(.*\)\n\(.*\)bar/bar1\1\r\2bar
ars1skjm

ars1skjm5#

:let dosubs=1
:%s/bar/\=[dosubs?'bar1':submatch(0),extend(g:,{'dosubs':!dosubs})][0]/g
nbewdwxp

nbewdwxp6#

下面是一个自定义命令,它使用一个 replace expression 来计算已完成的替换次数,并使用一个传递的附加参数来决定是否应该进行替换。(这允许更复杂的排列,而不是每隔一个。)这样,您的示例就简单了:

:%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 plugin

kr98yfug

kr98yfug7#

我找到了一个更简单的解决方案:

: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

另一个绝佳选择

:%s/<block>\n.*bar\zs/1

"我那复杂的老办法"

\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

相关问题