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

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”的每个块也可以。它虽然不是一个宏就可以获取整个文件,但那太棒了。

#1 楼

我认为以下命令应该起作用:

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


说明:

我们在整个文件上使用替换命令将pattern更改为string

:%s/pattern/string/


这里pattern^\(.*\)\(\n\)\+$string

pattern可以这样分解:

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


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

\(\)用来封装subpattern1,以便以后可以用特殊编号引用它。
它们也用来封装subpattern2,以便我们可以重复1个或多个\+subpattern1.*是与除换行符之外的任何字符匹配的元字符,而.是与最后一个字符匹配0、1或更多次的量词。匹配任何不包含新行的文本。

*.* subpattern2匹配换行,并且\n匹配第一个\n内的匹配文本,这里是

因此,\(可以像读取这是:行的开头(\)),然后是不包含新行的任何文本(subpattern1),然后是新行(pattern),然后是同一文本(^),后两个重复一次或多次(.*),并且

如果\n被匹配(同一行块),则替换命令将其替换为,这里是\+(该块的第一行)。

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

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


要进行更精细的控制,您还可以在更改每行代码之前要求确认,方法是添加string替代标志:

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


有关替代命令的更多信息阅读
了解替代标志hlsearch
了解各种元字符和量词,阅读n
了解vim中的正则表达式。通过在c的末尾添加:help :s来解决命令中的问题。

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

评论


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

–通配符
2015年11月5日在21:12

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

– saginaw
2015年11月5日在21:37

尝试:%s / ^ \(。* \)\(\ n \ 1 \)\ + $ / \ 1 /

–通配符
2015年11月5日在21:58

您应该考虑$匹配字符串的末尾,而不是行尾。从技术上讲,这是不正确的,但是当您在字符后加上一些例外字符时,它会匹配文字$而不是任何特殊字符。因此,对于多行匹配,使用\ n更好。 (请参阅:help / $)

–通配符
2015年11月5日在22:05

我认为您是对的,\ n可以在正则表达式内的任何位置使用,而$应该只在结尾使用。为了使两者有所不同,我编辑了答案,写为\ n匹配换行符(本能地使您认为后面还有一些文本),而$匹配行尾(使您认为没有什么东西留下)。

– saginaw
2015年11月5日在22:24

#2 楼

请尝试以下操作:

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


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


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

组件的含义是:


占整个文件的百分比

s替代

;开始替代字符串

\ v“ very magic”

^行首

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

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

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

;开始替换字符串

\ 1组1个匹配文本

;命令结束或开始标志


评论


我真的很喜欢您的答案,因为它的可读性更好,而且还因为它使我更好地了解了\ n和$之间的区别。 \ n在模式中添加了一些内容:字符换行,告诉vim以下文本在换行上。尽管$不会向模式中添加任何内容,但是如果模式之外的下一个字符不是换行符,则它只是禁止进行匹配。至少,这是我通过阅读您的答案和:help零宽度所了解的内容。

– saginaw
2015年11月6日14:35

对于^来说也必须如此,它不会向模式中添加任何内容,只会阻止模式之外的上一个字符不是换行符时进行匹配...

– saginaw
2015年11月6日14:37



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

–充血
15年11月6日,18:52



#3 楼

如果要删除所有相邻的相同行,而不仅仅是Hold,则可以使用vim内部的外部过滤器极其轻松地实现: />如果您想直接在:%!uniq中进行操作,实际上非常棘手。我认为有办法,但是对于一般情况来说,使其100%起作用是非常棘手的,而且我还没有弄清所有的错误。您可以直观地看到非重复的下一行不是以相同的字符开头,可以使用:

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


vim表示当前行。的。指当前行。 +表示(/^[^H]/-)下一行不以H开头的行。

然后d是delete。

评论


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

–充血
2015年11月6日19:06

通过外部工具提供文本的功能是为什么我通常在Windows上推荐Vim和Cygwin的原因。 Vim和Shell只是在一起。

–DevSolar
2015年12月3日,10:33

#4 楼

基于Vim的答案:

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


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

#5 楼

假设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>)


但这不一定比其他解决方案要好。

#6 楼

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


其根源在于%g/^\v(.*)\n$/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$/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$\)\+//e'

" Version from: Piet Delport <pjd {at} 303.za {dot} net>
" silent exe l1.','l2.'g/^\%<'.l2.'l\(.*\)\n$/d'