删除所有连续的重复项


13

我有一个看起来像这样的文件。

Move to 230.00
Hold
Hold
Hold
Hold
Hold
Hold
Move to 00.00
Hold 
Hold 
Hold 
Hold 
Hold 
FooBar
Hold 
Spam
Hold

我希望它看起来像这样:

Move to 230.00
Hold
Move to 00.00
Hold 
FooBar
Hold
Spam
Hold

我敢肯定,vim必须有一种方法可以快速地做到这一点,但是我不太清楚如何做。这是否超出了宏的功能,并且需要vimscript?

另外,如果必须将相同的宏应用于“ Holds”的每个块,也可以。它不必是一个宏即可获取整个文件,尽管那真是太棒了。

Answers:


13

我认为以下命令应该工作:

 :%s/^\(.*\)\(\n\1\)\+$/\1/

说明:

我们在整个文件上使用替换命令更改patternstring

:%s/pattern/string/

这里pattern^\(.*\)\(\n\1\)\+$,现在string\1

pattern 可以这样分解:

^\(subpattern1\)\(subpattern2\)\+$

^$分别匹配行首和行尾。

\(\)用来括起来,subpattern1这样我们以后可以用特殊数字来引用它\1
它们也用于封闭,subpattern2以便我们可以使用量词将其重复1次或更多次\+

subpattern1.*
.与除换行符以外的任何字符匹配的元字符,并且*是与最后一个字符匹配0、1或更多次的量词。
因此.*匹配任何不包含新行的文本。

subpattern2\n\1
\n相匹配的新行,\1这是里面的第一个匹配相同的文本相匹配\(\)在这里是subpattern1

因此pattern可以这样看:
第(^)行的开头,然后是不包含新行(.*)的任何文本,然后是新行(\n),然后是相同的文本(\1),后两个重复一次或多次(\+),并且最后是($)行的结尾

无论在哪里pattern匹配(相同行的块),替换命令都将其替换为string此处\1(该行的第一行)。

如果要在不更改文件中任何内容的情况下查看将影响哪些行,可以启用该hlsearch选项并n在命令末尾添加替换标志:

:%s/^\(.*\)\(\n\1\)\+$/\1/n

为了进行更精细的控制,您还可以通过添加c替换标志来更改每个行之前要求确认:

:%s/^\(.*\)\(\n\1\)\+$/\1/c

有关替换命令读取更多的信息:help :s
用于替代标志:help s_flags
对各种元字符和量词阅读:help pattern-atoms
并在VIM正则表达式阅读

编辑:通配符通过$在末尾添加a来解决命令中的问题pattern

而且BloodGain具有相同命令的更短且更易读的版本。


1
不错 您的命令中需要一个$。否则,它将以与上一行相同的文本开头的行,但还有其他一些尾随字符,来做意外的事情。还要注意,您给出的基本命令在功能上等同于我对的回答:%!uniq,但是高亮和确认标志很好。
2015年

没错,我刚刚检查了一下,如果重复的行之一包含不同的结尾字符,则该命令的行为与预期的不同。我不知道如何解决它,原子\n匹配行尾并且应该防止这种情况,但事实并非如此。我尝试添加一次$之后.*没有成功。我将尝试修复它,但是如果无法解决,也许我会删除答案或在最后添加警告。感谢您指出这个问题。
萨吉诺2015年

1
尝试:%s/^\(.*\)\(\n\1\)\+$/\1/
2015年

1
您应该考虑$匹配字符串的结尾,而不是行尾。从技术上讲,这是不正确的,但是当您将字符放在几个例外之后之后时,它将匹配文字$而不是任何特殊字符。因此,使用\n多行匹配更好。(请参阅:help /$
2015年

我认为您是对的,因为它\n可以在正则表达式中的任何位置使用,而$应该只在最后使用。为了使两者有所不同,我通过编写\n匹配换行符(本能地使您认为后面还有一些文本)而$匹配行尾(这使您认为没有任何内容)来编辑答案剩下)。
萨吉诺2015年

10

请尝试以下操作:

:%s;\v^(.*)(\n\1)+$;\1;

saginaw的答案一样,它使用Vim的:substitute命令。但是,它利用了几个额外的功能来提高可读性:

  1. Vim允许我们使用除反斜杠(\),双引号()或竖线(|)之外的任何非字母数字ASCII字符来划分我们的匹配/替换/标志文本。在这里,我选择了分号(;),但是您可以选择另一个。
  2. Vim为正则表达式提供了“魔术”设置,因此可以解释字符的特殊含义,而无需反斜杠转义。这有助于减少冗长,并且比“ nomagic”默认值更为一致。以\v“。” 开头的意思是“非常魔术”,或者除字母数字(A-z0-9)和下划线(_)以外的所有字符都有特殊含义。

这些组件的含义是:

整个文件的百分比

小号 替代

; 开始替换字符串

\ v “非常神奇”

^ 行首

(。*) 0个或多个任何字符(组1)

(\ n \ 1)+ 换行符,后跟(第1组匹配文本),1次或更多次(第2组)

$ 行尾(或在这种情况下,认为下一个字符必须是换行符

; 开始替换字符串

\ 1 组1个匹配文字

; 命令结束开始标志


1
我真的很喜欢你的答案,因为它更具有可读性,还因为它让我更好地理解之间的差异\n$\n向模式中添加一些内容:字符换行,告诉vim以下文本在换行上。尽管$不向模式添加任何内容,但是如果模式之外的下一个字符不是换行符,则它只是禁止进行匹配。至少,通过阅读您的答案和,我已经了解了这一点:help zero-width
萨吉诺2015年

同样^,它必须正确,它不会向模式中添加任何内容,只是防止了模式之外的上一个字符不是换行符时进行的
匹配

@saginaw您完全正确,这是一个很好的解释。在正则表达式中,某些字符可以作为控制字符。例如,+意思是“重复前面的表达式(字符或组)1次或更多次”,但其本身不匹配。的^意思是“不能在字符串中间开始” $的意思是“不能在字符串中间结束。” 请注意,我不是在说“线”,而是在这里说“弦”。Vim默认情况下将每一行都视为字符串-就是这样\n了。它告诉Vim消耗一个换行符来尝试进行此匹配。
Bloodgain 2015年

8

如果要删除所有相邻的相同行,而不仅仅是Hold,可以使用以下内部过滤器非常轻松地完成此操作vim

:%!uniq (在Unix环境中)。

如果您想直接在中进行操作vim,这实际上非常棘手。我认为有办法,但是对于一般情况来说,使其100%正常运行是非常棘手的,而且我还没有解决所有的错误。

但是,对于这种特定情况,由于您可以直观地看到非重复的下一行不是以相同的字符开头,因此可以使用:

:+,./^[^H]/-d

+指当前行之后的行。的。指当前行。该/^[^H]/-机构之前(线-)的下一个不与H.开始行

然后d被删除。


3
尽管替代命令和全局Vim命令是不错的练习,但uniq我要如何解决(从vim内部或使用shell)调用。一方面,我很确定uniq将空白行/所有行都等效(未测试),但是用正则表达式捕获起来会困难得多。这也意味着在我要完成工作时不要“重新发明轮子”。
Bloodgain

2
通过外部工具提供文本的功能是为什么我通常在Windows上推荐Vim Cygwin的原因。Vim后壳只是属于一起。
DevSolar 2015年

2

基于Vim的答案:

:%s/\(^.*\n\)\1\{1,}/\1

=用同一行替换每行其后至少一次


2

再假设Vim 7.4.218或更高版本:

function! s:Uniq(line1, line2)
    let cursor = getcurpos()
    let lines = uniq(getline(a:line1, a:line2))
    if setline(a:line1, lines) == 0 && len(lines) <= a:line2 - a:line1
        silent execute (a:line1 + len(lines)) . ',' . a:line2 . 'd _'
    endif
    call setpos('.', cursor)
endfunction

command! -range=% Uniq call <SID>Uniq(<line1>, <line2>)

不过,这不一定比其他解决方案好。


2

这是一个基于Preben Gulberg和Piet Delport 的旧(2003)vim(golf)的解决方案。

  • 根源在于 %g/^\v(.*)\n\1$/d
  • 与其他解决方案不同,它已被封装到一个函数中,因此它不会修改搜索寄存器,也不会修改未命名的寄存器。
  • 并且它也被封装到命令中以简化其用法:
    • :Uniq(相当于:%Uniq),
    • :1,Uniq (从缓冲区的开始到当前行),
    • 直观地选择行+匹配:Uniq<cr>(通过vim扩展为:'<,'>Uniq
    • 等等(:h range

这是代码:

command! -range=% -nargs=0 Uniq <line1>,<line2>call s:EmuleUniq()

function! s:EmuleUniq() range
  let l1 = a:firstline
  let l2 = a:lastline
  if l1 < l2
    " Note the "-" to avoid spilling over the end of the range
    " Note also the use of ":delete", along with the black hole register "_"
    silent exe l1.','l2.'-g/^\(.*\)\n\1$/d _'

    call histdel('search', -1)          " necessary
    " let @/ = histget('search', -1)    " useless within a function
  endif
endfunction

注意:他们的首次尝试是:

" Version1 from: Preben 'Peppe' Guldberg <peppe {at} xs4all {dot} nl>
" silent exe l1 . ',' . (l2 - 1) . 's/^\(.*\)\%(\n\%<' . (l2 + 1)
      " \ . 'l\1$\)\+/\1/e'

" Version from: Piet Delport <pjd {at} 303.za {dot} net>
" silent exe l1.','l2.'g/^\%<'.l2.'l\(.*\)\n\1$/d'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.