在Vim中删除除正则表达式匹配之外的所有内容

18

我的具体情况是一个包含大量文本和IPv4地址的文档。我想删除除IP地址以外的所有内容。

我可以使用:vglobal搜索([0-9]{1,3}\.){3}[0-9]{1,3}并删除没有IP地址的所有行,但之后我只知道如何搜索整行并选择匹配的文本。有更简单的方法吗?

简而言之,我正在寻找一种不使用外部程序(如grep)来执行以下操作的方法:

grep --extended-regexp --only-matching --regexp="([0-9]{1,3}\.){3}[0-9]{1,3}"

从vim调用grep可能需要调整我的正则表达式(例如:删除\v)。使用vim的增量搜索可以显示我已经正确匹配了模式,而且我不想在grep中验证我的正则表达式。


编辑: 感谢Peter,这是我现在使用的函数。(C是我通常在函数中破坏的寄存器。)

"" Remove all text except what matches the current search result
"" The opposite of :%s///g (which clears all instances of the current search).
function! ClearAllButMatches()
    let old = @c
    let @c=""
    %s//\=setreg('C', submatch(0), 'l')/g
    %d _
    put c
    0d _
    let @c = old
endfunction

编辑2:我将其制作成一个接受范围的命令(但默认为整个文件)。

"" Remove all text except what matches the current search result. Will put each
"" match on its own line. This is the opposite of :%s///g (which clears all
"" instances of the current search).
function! s:ClearAllButMatches() range
    let is_whole_file = a:firstline == 1 && a:lastline == line('$')

    let old_c = @c

    let @c=""
    exec a:firstline .','. a:lastline .'sub//\=setreg("C", submatch(0), "l")/g'
    exec a:firstline .','. a:lastline .'delete _'
    put! c

    "" I actually want the above to replace the whole selection with c, but I'll
    "" settle for removing the blank line that's left when deleting the file
    "" contents.
    if is_whole_file
        $delete _
    endif

    let @c = old_c
endfunction
command! -range=% ClearAllButMatches <line1>,<line2>call s:ClearAllButMatches()

1
由于您正在使用函数,因此可以停止破坏。这是通过在函数开头保存@c寄存器(let old = @c)并在结尾处恢复它(let @c = old)来完成的。 - Peter Rincker
相关:在Vim SE上,如何删除不符合模式的行的一部分? - kenorb
4个回答

14

可以通过使用子替换特殊字符和setreg()逐行替换实现此效果。

:let @a=""
:%s//\=setreg('A', submatch(0), 'l')/g
:%d _
:pu a
:0d _

或者全部放在一行中,如下所示:

:let @a=""|%s//\=setreg('A', submatch(0), 'l')/g|%d _|pu a|0d _

概述:使用替换将每个匹配项逐行附加到寄存器"a"中,然后用寄存器"a"的内容替换整个缓冲区。

解释:

  1. let @a="" 清空我们将要附加的“a”寄存器
  2. %s//\=setreg('A', submatch(0), 'l')/g 全局替换使用最后一个模式
  3. \=expr 将模式替换为表达式的内容
  4. submatch(0) 获取刚刚匹配到的整个字符串
  5. setreg('A', submatch(0), 'l') 将匹配的字符串附加(注意:是大写字母“A”)到@a寄存器中,但逐行进行
  6. %d _ 删除所有行,并将其保存到黑洞寄存器(即@_)
  7. pu a 将@a寄存器的内容放入缓冲区
  8. 0d _ 删除第一行

注意事项:

  • 这将使你的一个寄存器失效。本例中使用的是@a寄存器
  • 使用最后一次搜索模式。尽管你可以修改替换命令,使用任何你想要的模式:%s/<pattern>/\=setreg('A', submatch(0), 'l')/g

了解更多帮助信息

:h :s\=
:h :let-@
:h submatch()
:h setreg()
:h :d
:h :p

这是一个很棒的宏。谢谢。 - Mert Nuhoglu

8
假设<ip>是用于匹配IP地址的正则表达式,我认为你可以这样做:
:%s/.\{-}\(<ip>\).*/\1/g

其中\1表示第一个匹配组(只匹配地址),.\{-}用于非贪婪匹配。


1
就像我说的那样,我希望避免搜索整行并匹配文本,因为这种方式很难输入。你的解决方案可以使用非常神奇的正则表达式(\v)来缩短::%s/\v.{-}(<ip>).*/\1/。此外,它需要vglobal步骤。 - idbrii
为什么不定义一个函数,将vglobal步骤和icecrime的步骤一起完成,然后定义一些按键映射来调用该函数并将其粘贴到您的.vimrc中?只需按下一个按键,就可以完成所有操作...一个按键有多难打呢? - frabjous
如果一行中有多个IP地址,它也无法工作。(它只会取第一个IP地址。) - idbrii
我使用了一个稍微修改过的版本::%s/.\{-}\(<ip>\)/\1 /g,用于删除文件中除了所有与<ip>匹配的内容之外的所有内容,并将所有匹配项用单个空格分隔。我唯一发现的缺点是“尾部”(即文件末尾的非匹配文本)仍然存在,但很容易删除,并且可能可以由比我更熟悉VIM的人自动化处理;-) - Fabian Streitel

6
简而言之,我正在寻找一种在不离开vim的情况下完成此操作的方法。
很简单:
:1,$! grep --extended-regexp --only-matching --regexp="([0-9]{1,3}\.){3}[0-9]{1,3}"

(虽然我实际上投赞成票了icecrime的替代答案)

1
这就是我所说的离开vim的意思。 - idbrii
这基本上是我目前尝试改进的解决方案:cabbrev selectgrep !egrep -oe""<Left><BS> - idbrii
可以给我一些指针,让我了解命令的第一部分:惊叹号前面的 1,$ 吗? - Jikku Jose
1
@JikkuJose:只需使用:line命令或:line,line命令来指定范围。行可以是数字、$表示结尾,或者像/hello/这样的模式。如果vim的手册不够用,sed的手册会给你提供大量信息。 - Ben Jackson

6
:set nowrapscan
:let @a=""
gg0qac/\v(\d{1,3}\.){3}\d{1,3}<CR><CR><Esc>//e+1<CR>@aq@adG

说明:

  1. set nowrapscan 禁用了在“文件末尾”继续查找的功能。
  2. let @a="":清空寄存器 a。
  3. gg0:跳转到第一行(gg)的第一列(0)。
  4. qa:开始录制宏。
  5. c/{pattern}<CR>:更改至 pattern。
  6. c{motion}<CR><ESC>:将文本替换为换行符(此处 {motion}/{pat}<CR>)。
  7. //e+1<CR>:搜索最后一个模式,向左移动一个字符以超出其末尾(围绕换行符包装,但如果您的行看起来像这样:IP<newline>IP,可能会有问题)。
  8. @a:执行 @a 宏(当您录制它时,它为空,但完成后将重复步骤 1-7 直到出错)。
  9. q:结束录制 @a
  10. @a:执行 @a 宏。
  11. dG:删除到文件末尾。

不错,那个自我重复的宏是我已经思考了相当长时间的东西。 - Ressu
我完全忘记了 nowrapscan。使用自重复宏和搜索的非常流畅的方式。干杯! - Peter Rincker
你如何在映射或函数中使用这个答案?当传递给 normal 时,gg0qac... 行的工作方式不同。似乎没有发送 q 结束录制。同样,一个更简单的 normal 命令,使用递归宏删除正斜杠也无法正常工作:normal qaqqaf/x@aq,并将其后半部分用作映射得到相同的结果。 - idbrii

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接